Sunday, May 23, 2010

Installation of applications on the SD card with Froyo : the how- to !

Youpeee !!!
Froyo is finally here, sooner than I expected it !Thanks, Google !!!

Now, like every other geek with their new toy in hand, I tried most of the new things...
And all is great, but...
The install on SDCard is always greyed...

Then, I just read a tweet from Romain Guy explaining it in a few words.
Your application should add this line in the manifest

 xmlns:android="http://schemas.android.com/apk/res/android"
    android:installLocation="auto"
    ... >

in order to be installed by the end user on the SD Card.

http://developer.android.com/intl/fr/guide/appendix/install-location.html

Friday, May 21, 2010

libraries in Android : the new feature no body speak about in the SDK

Hi all,

This Google I/O has been rich in announces and new and nice features.

So everybody is speaking about :
*  flash : This is really nice, and a big plus for Android ( no, I didn't mention the Fruit Phone... )
* performances : x2 - x5 for free is always good for developers. It also seems the general OS has been improved in this direction.
* Teethering
* Installation on SD card ( the size of applications will now explode !! )
* Cloud to device messenging : this is like the push function in the Fruit Phone, but one step further, as it use the intent system, and can launch some application from the Cloud...
* Application Backup Data : This is nice. Some developers were already doing it on their side, but having it included in the SDK is really a big plus.


But there is a little new feature that I was waiting for for such a long time, and that is totally unexpected :
The project Libraries !!

Project libraries are basically, Android libraries, with code and resources included.
This is awesome !!

It means that now, we can finally have one common code for a free and a paid application !
In my case, I have 2 different application for the english and the french version of my game, I will now have only one version !

Here is the thing :
http://developer.android.com/intl/fr/guide/developing/eclipse-adt.html#libraryProject

And there is a sample :
http://developer.android.com/intl/fr/resources/samples/TicTacToeMain/index.html

Enjoy !!!

Friday, April 30, 2010

Google added crash report feedback for developers !

Hi all,

I was telling it over and over again : crash reports are essential for developers, to make their apps more stable, and user experience more enjoyable.
It looks like google agree with this statement, as they added some crash reports in the developer page :











This is very good news, as it will make the Android platform a better platform to develop on.

How does it work ?
It looks like when a application crashed, the phone let the user send a feedback to the developer( with a custom message from the user, the callstack, and ... ??? ).
I still don't know what is in this feedback - as I got 0 reports - but it definitively looks interesting !
You can also see that is also reports freezes, that user made crash reporters can not intercept !
The developer can then mark a report as 'old' ( so it won't appear again ? ).

For the moment it is still at experimental state. For instance, it looks like you can check feedbacks from other application, but I couldn't get it to work ( having 'server errors' ), and I find it strange for privacy reasons.

This is good to see that Android is going in the right direction !

Tuesday, March 30, 2010

How to improve your application : a crash reporter to improve stability ( slight return )

Hi all,

For quite some time, I wanted to return on my crash reporter, and on this article here.

Actually, there are two things that I changed for my crash reporter :
* First, I made it more robust, fixing a bug (that I can't understand, by the way) in it.
* Then, I extended the crash reporter so that it can be even more useful to help me ( and you ? ) in the bug hunt...

1) Making the crash reporter more robust :

At the launch of my crash reporter, I was gathering informations on the application environment, to give me more information on the system the bug was happening on.
The code I was using was this one :
void RecoltInformations( Context context )
{
PackageManager pm = context.getPackageManager();
        try
        {
         PackageInfo pi;
            // Version
            pi = pm.getPackageInfo(context.getPackageName(), 0);
            VersionName = pi.versionName;
            // Package name
            PackageName = pi.packageName;
            // Files dir for storing the stack traces
            FilePath = context.getFilesDir().getAbsolutePath();
            // Device model
            PhoneModel = android.os.Build.MODEL;
            // Android version
            AndroidVersion = android.os.Build.VERSION.RELEASE;
        
            Board = android.os.Build.BOARD;
            Brand  = android.os.Build.BRAND;
            //CPU_ABI = android.os.Build.;
            Device  = android.os.Build.DEVICE;
            Display = android.os.Build.DISPLAY;
            FingerPrint = android.os.Build.FINGERPRINT;
         Host = android.os.Build.HOST;
         ID = android.os.Build.ID;
         //Manufacturer = android.os.Build.;
         Model = android.os.Build.MODEL;
         Product = android.os.Build.PRODUCT;
         Tags = android.os.Build.TAGS;
         Time = android.os.Build.TIME;
         Type = android.os.Build.TYPE;
         User = android.os.Build.USER;
        
        }
        catch (NameNotFoundException e)
        {
                e.printStackTrace();
        }
}
And this code was crashing on some phones...
I still can't understand where this code can crash ( if you have some ideas, I would happily hear them ! ), but it is actually quite easy to fix :
* Catching all the exceptions, and incorporating the getPackageManagerPart in the try part so that its eventual failure would be caught.
* Not calling this function at the start of the application, but only when it really is needed : when a bug occcurs. There was really no need to call it before.

With this sole improvement, I could prevent the crash reporter to crash ( having a debug tool that creates bug is a _bad_ thing ), but still, I was receiving bugs with this info :

Informations :
==============
Version : null
Package : null
FilePath : /data/data/com.alocaly.LetterGame/files
Phone Modelnull
Android Version : null
Board : null
Brand : null
Device : null
Display : null
Finger Print : null
Host : null
ID : null
Model : null
Product : null
Tags : null
Time : 0
Type : null
User : null
Total Internal memory : 274464768
Available Internal memory : 209596416

It means, first that this code is no more crashing and then that the code that was crashing was in the very first lines of the function. So thrown by GetPackageManager, GetPackageName or GetPackageInfo. 
But, from the Android documentation, only the NameNotFoundException could be launched by these functions... And it was handled by my previous code !!
Conclusion : I still don't understand what was happening !


2) Extending the crash reporter.

Taking some time to think about this crash reporter, we could wonder what is the primary goal of this tool. The real goal of this tool is to give us more information on the bugs that occured so that we can understand remotely what was really happening.
The callstack is a very valuable piece of information to get, the environment can give you some clues, but this is just not enough. Trying to understand where a bug lay with so few information is very tricky. 
But we can't add more informations, without doing something really specific for one application...
...
And that was the good idea : we obviously need some information specific for the application !!!
The application developer knows what kind of information would help him understand the bugs !

For instance, in my game 'Word Prospector', I finally had some crash in the function that checks whether or not a word is correct. Knowing what this strange-crash-creating-word is would be very valuable information.

So I decided to add in my crash reporter a way to add any informations. I just added a hash map with some Key/Values strings, add some functions to populate the hash map, and display this hash map in the report the crash reporter is creating when a bug occurs.

This was a really good idea ! I could find that this word was starting with a '?' : the user could enter some letters while quitting the pause state, where all the letters are replaced by some question marks...


So now, when there is a suspicious and hard to understand crash, I can had a lot of value to check, release a new version, and wait in front of my mail box !

Here is the new version of the code.
I still init it from the application object, so I initialize it for the whole game with just one call.


public class ErrorReporter implements Thread.UncaughtExceptionHandler
{
String VersionName;
String PackageName;
String FilePath;
String PhoneModel;
String AndroidVersion;
String Board;
String Brand;
String Device;
String Display;
String FingerPrint;
String Host;
String ID;
String Manufacturer;
String Model;
String Product;
String Tags;
long Time;
String Type;
String User;
HashMap CustomParameters = new HashMap< String, String>();

private Thread.UncaughtExceptionHandler PreviousHandler;
private static ErrorReporter S_mInstance;
private Context CurContext;

public void AddCustomData( String Key, String Value )
{
CustomParameters.put( Key, Value );
}

private String CreateCustomInfoString()
{
String CustomInfo = "";
Iterator iterator = CustomParameters.keySet().iterator();
while( iterator.hasNext() )
{
String CurrentKey = iterator.next();
String CurrentVal = CustomParameters.get( CurrentKey );
CustomInfo += CurrentKey + " = " + CurrentVal + "\n";
}
return CustomInfo;
}

static ErrorReporter getInstance()
{
if ( S_mInstance == null )
S_mInstance = new ErrorReporter();
return S_mInstance;
}

public void Init( Context context )
{
PreviousHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler( this );
CurContext = context;
}

public long getAvailableInternalMemorySize() {
        File path = Environment.getDataDirectory();
        StatFs stat = new StatFs(path.getPath());
        long blockSize = stat.getBlockSize();
        long availableBlocks = stat.getAvailableBlocks();
        return availableBlocks * blockSize;
    }
    
    public long getTotalInternalMemorySize() {
        File path = Environment.getDataDirectory();
        StatFs stat = new StatFs(path.getPath());
        long blockSize = stat.getBlockSize();
        long totalBlocks = stat.getBlockCount();
        return totalBlocks * blockSize;
    }

void RecoltInformations( Context context )
{
        try
        {
     PackageManager pm = context.getPackageManager();
         PackageInfo pi;
            // Version
            pi = pm.getPackageInfo(context.getPackageName(), 0);
            VersionName = pi.versionName;
            // Package name
            PackageName = pi.packageName;
            // Device model
            PhoneModel = android.os.Build.MODEL;
            // Android version
            AndroidVersion = android.os.Build.VERSION.RELEASE;
          
            Board = android.os.Build.BOARD;
            Brand  = android.os.Build.BRAND;
            Device  = android.os.Build.DEVICE;
            Display = android.os.Build.DISPLAY;
            FingerPrint = android.os.Build.FINGERPRINT;
         Host = android.os.Build.HOST;
         ID = android.os.Build.ID;
         Model = android.os.Build.MODEL;
         Product = android.os.Build.PRODUCT;
         Tags = android.os.Build.TAGS;
         Time = android.os.Build.TIME;
         Type = android.os.Build.TYPE;
         User = android.os.Build.USER;
          
        }
        catch( Exception e )
        {
         e.printStackTrace();
        }
}

public String CreateInformationString()
{
RecoltInformations( CurContext );

String ReturnVal = "";
ReturnVal += "Version : " + VersionName;
ReturnVal += "\n";
ReturnVal += "Package : " + PackageName;
ReturnVal += "\n";
ReturnVal += "FilePath : " + FilePath;
ReturnVal += "\n";
ReturnVal += "Phone Model" + PhoneModel;
ReturnVal += "\n";
ReturnVal += "Android Version : " + AndroidVersion;
ReturnVal += "\n";
ReturnVal += "Board : " + Board;
ReturnVal += "\n";
ReturnVal += "Brand : " + Brand;
ReturnVal += "\n";
ReturnVal += "Device : " + Device;
ReturnVal += "\n";
ReturnVal += "Display : " + Display;
ReturnVal += "\n";
ReturnVal += "Finger Print : " + FingerPrint;
ReturnVal += "\n";
ReturnVal += "Host : " + Host;
ReturnVal += "\n";
ReturnVal += "ID : " + ID;
ReturnVal += "\n";
ReturnVal += "Model : " + Model;
ReturnVal += "\n";
ReturnVal += "Product : " + Product;
ReturnVal += "\n";
ReturnVal += "Tags : " + Tags;
ReturnVal += "\n";
ReturnVal += "Time : " + Time;
ReturnVal += "\n";
ReturnVal += "Type : " + Type;
ReturnVal += "\n";
ReturnVal += "User : " + User;
ReturnVal += "\n";
ReturnVal += "Total Internal memory : " + getTotalInternalMemorySize();
ReturnVal += "\n";
ReturnVal += "Available Internal memory : " + getAvailableInternalMemorySize();
ReturnVal += "\n";

return ReturnVal;
}

public void uncaughtException(Thread t, Throwable e)
{
String Report = "";
Date CurDate = new Date();
Report += "Error Report collected on : " + CurDate.toString();
Report += "\n";
Report += "\n";
Report += "Informations :";
Report += "\n";
Report += "==============";
Report += "\n";
Report += "\n";
Report += CreateInformationString();

Report += "Custom Informations :\n";
Report += "=====================\n";
Report += CreateCustomInfoString();

Report += "\n\n";
Report += "Stack : \n";
Report += "======= \n";
final Writer result = new StringWriter();
final PrintWriter printWriter = new PrintWriter(result);
e.printStackTrace(printWriter);
String stacktrace = result.toString();
Report += stacktrace;

Report += "\n";
Report += "Cause : \n";
Report += "======= \n";

// If the exception was thrown in a background thread inside
// AsyncTask, then the actual exception can be found with getCause
Throwable cause = e.getCause();
while (cause != null)
{
cause.printStackTrace( printWriter );
Report += result.toString();
cause = cause.getCause();
}
printWriter.close();
Report += "****  End of current Report ***";
SaveAsFile(Report);
//SendErrorMail( Report );
PreviousHandler.uncaughtException(t, e);
}

private void SendErrorMail( Context _context, String ErrorContent )
{
Intent sendIntent = new Intent(Intent.ACTION_SEND);
String subject = _context.getResources().getString( R.string.CrashReport_MailSubject );
String body = _context.getResources().getString( R.string.CrashReport_MailBody ) +
"\n\n"+
ErrorContent+
"\n\n";
sendIntent.putExtra(Intent.EXTRA_EMAIL,
new String[] {"postmaster@alocaly.com"});
sendIntent.putExtra(Intent.EXTRA_TEXT, body);
sendIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
sendIntent.setType("message/rfc822");
_context.startActivity( Intent.createChooser(sendIntent, "Title:") );
}
private void SaveAsFile( String ErrorContent )
{
try
{
Random generator = new Random();
int random = generator.nextInt(99999);
String FileName = "stack-" + random + ".stacktrace";
FileOutputStream trace = CurContext.openFileOutput( FileName, Context.MODE_PRIVATE);
trace.write(ErrorContent.getBytes());
trace.close();
}
catch( Exception e )
{
// ...
}
}

private String[] GetErrorFileList()
{
File dir = new File( FilePath + "/");
        // Try to create the files folder if it doesn't exist
        dir.mkdir();
        // Filter for ".stacktrace" files
        FilenameFilter filter = new FilenameFilter() {
                public boolean accept(File dir, String name) {
                        return name.endsWith(".stacktrace");
                }
        };
        return dir.list(filter);
}
private boolean bIsThereAnyErrorFile()
{
return GetErrorFileList().length > 0;
}
public void CheckErrorAndSendMail(Context _context )
{
try
{

FilePath = _context.getFilesDir().getAbsolutePath();
if ( bIsThereAnyErrorFile() )
{
String WholeErrorText = "";
// on limite à N le nombre d'envois de rapports ( car trop lent )
String[] ErrorFileList = GetErrorFileList();
int curIndex = 0;
final int MaxSendMail = 5;
for ( String curString : ErrorFileList )
{
if ( curIndex++ <= MaxSendMail )
{
WholeErrorText+="New Trace collected :\n";
WholeErrorText+="=====================\n ";
String filePath = FilePath + "/" + curString;
BufferedReader input =  new BufferedReader(new FileReader(filePath));
String line;
while (( line = input.readLine()) != null)
{
WholeErrorText += line + "\n";
}
input.close();
}

// DELETE FILES !!!!
File curFile = new File( FilePath + "/" + curString );
curFile.delete();
}
SendErrorMail( _context , WholeErrorText );
}
}
catch( Exception e )
{
e.printStackTrace();
}
}
}

Enjoy it !!


NOTE : I'm no more using this mail technic. Now I'm using ACRA, and the results are much more interesting !! See here for more info.


Ps : here is a link I found while googling :
http://web-authoring.seadvd.com/whats-google-up-to-with-android/
:)

Tuesday, March 2, 2010

Google really takes care of developers !

Google really want developers to be able to develop in the best conditions !

15 days ago, I was in Montreal for my real job...
So I missed the Android Developer Lab in Paris ... and the Nexus One that was given to every attendant !
Then there was an Android Developer Lab during the MWC, in Barcelona... with a new Nexus One distribution...
There will also be a phone distribution at the Game Developer Conference.

And now, I've just received this email :
"Due to your contribution to the success of Android Market, we would like to present you with a brand new Android device as part of our developer device seeding program. You are receiving this message because you're one of the top developers in Android Market with one or more of your applications having a 3.5 star or higher rating and more than 5,000 unique downloads. "

That is really nice !

Actually, I think Google is doing the righ thing... Not only by giving ME a new phone, but by taking care of developers.

The iPhone success has several reasons.
For sure, the phone was really nice, and came with an interface never seen before... But the most important thing, in my opinion, is that they let any developer develop an application for the iPhone. This is why there are so many application on the iPhone, this is why there are so many good applications on the iPhone, so many new little ideas that make the phone really different...
This is such a big thing that this is what they based their ad campaign on !

Archos understood the importance of developers too, by offering a substancial reduction on their android tablet to developers.
And now Google decided to follow the same way.

This is good for developers, this is a good sign for Android - showing how much Google really involves in Android -, this is good for customers...

Thank you, google !!

IMPORTANT UPDATE :
I received this email at 1.00 AM, the email was coming from Eric Chu, who always send us developers mails about Android updates, so I didn't even ask myself about the mail validity.
And this morning, a lot of people were wondering whether it is a fake or a real mail :
The registration form this mail send to us is hosted on Google Site, the google free hosting service, and it's basically filling a document on google docs. So it really could be a Fake mail, trying to collect some 'Google Order Number' ( Can we do something harmful with it ? )

But it now has been validated by google, by several means, including a post by Roman Nurk, whose title is "Android Developer Relations" in this google group :
http://groups.google.com/group/android-developers/browse_thread/thread/49da01a3cb7f8803

Conclusion : you really can trust this mail....

Saturday, January 30, 2010

Who said an Android phone should fit in your pocket ?

The iPad looks like 'just' a bigger iPhone, but there is already 'just' a bigger Android phone :





By the way, speaking of the iPad, I have a hard time thinking Apple wants to make it the best Internet device, WITHOUT Flash !!
And it looks like, from the Adobe blog, flash is still not coming in Apple's World ( link to the blog article )

Thursday, January 21, 2010

Discovering Flurry

First, I just want to point that I'm not in any way affiliated with Flurry, and blablahbla.... :
NO I DON'T KNOW THOSE GUYS... I just tried their tool !!


What is Flurry ?
When you launch a new application, it's always great to have some feedbacks on it.
There are many different feedbacks : error reports, user evaluations, user mails, online scores, or... 'some' statistics.
It's useful for two different things :
* for your ego, it's always a good thing to have some feedback that basically says that people are trying your application. It's even better if they say they like it ! Even if you don't have the super-developed ego that most developers have, having this kind of information is good for motivation, and helps you improve your application ! As strange as this point may sound, I think it is really important specially if you are an independant developer, as you will need some motivation !
* It's  also crucial if you want to know who your application users are, and how they use it ! It will let you analyse what is good in your application, what are the weaknesses, where to put your efforts in order to satisfy your users...

As I had embedded AdMob ( the now famous ads provider for Android ), I already had some sort of feedback for a while : the number of ads impression for every day. It gives me an indication on how many users are playing my game, and on how this number is evolving.

Flurry ( actually Flurry Analytics ) gives you a lot more statistics on the users and on how they use your application.

How to integrate Flurry ?
The integration of Flurry is really easy.
Create an account on their web site, register your application, add the jar in your application, and copy paste some code on each activity, and voilà ! your first integration is done !!
I also added some special events, in order to know which activity was started, and which game mode was used ( the game has 6 different game modes ).
This is a very good point with this middleware !!


What indications Flurry provides you ?

Flurry provides you informations on :
* users : how many users you have, whether they keep on using your application, how many times did they use it every day, how many sessions you have, how long is a session, and where do they come from.
* Technical : info on which phone was used, which carrier, which firmware, and a simple error reporter is provided ( that does not seem to interfere with mine !! )
* Events : Events are special signals you put in your applications and that are collected by Flurry. You basically do whatever you want with them !

By the way, the information is nicely presented, with some beautiful graphes that make it easy to get the information quickly !


How useful is this information ?

To be honest, I'm kind of fascinated by all this feedback !
All of it is not useful, but it's really interesting to navigate throw this pages and graphes on my game !

In the user pages, you can see the evolution of the way your application is used : if it keeps on increasing.
For instance, I really did see the Chrismas effect : the number of new users has really increased after the 25 !



Welcome to all of those new Android Owners !!



The technical part is also really interesting :
* The firmware versions are divided in about one big third Android 1.5, another third Android 2.0.1, and another little third is Android 1.6...
The rest ( yes, the rest after the 3 thirds... ) is Android 2.0 and Android 2.1. That means that if you want to bypass one of this Android version, you are limiting your audience in large proportions !








* The devices : The droid is the big winner here, with about 1/3 of the devices used. Then the HTC Hero / Eris, the HTC G1/Dream and the MyTouch/Magic...
We can observe that the french version of my game, obviously mainly played in France shows a very different picture :
WordProspector version ( essentially played in the US )



"Chasseur de mots" ( the french version, essentially played in France )


We can see that Samsung - that had shipped the Galaxy a long time ago in France - is much more present, and that Motorola is surprisingly unpresent !
Another funny thing : in the french version there is a ... BlackBerry 8230 : I guess someone played with home-made OS and changed the Phone_Model string !

Last point : the events
The events can clearly be the most important point in Flurry if you use them wisely.
If you want to know something specific on how your users are using your application, Events are for you !
For instance, in my game, I registered which game mode the player were playing.
Personally, I always play the game in Full Game mode, 1 minute, but I discovered that  most people played in 'simple mode', for 1 minute.


Conclusion :
Clearly, users want some quick game : both 'simple game' and 'full game' are played.
And the 2 minutes and 3 minutes games are far from being negligeable ( at that point, I was still wondering if I had developed that for more than 3 users :)


Conclusion : was it worth it ?

First, I want to point that, obviously, Flurry your application to open network and localization authorizations. I don't think it is a real issue ( and in my case, I already needed these authorizations for AdMob), but it may be something that you don't want.

If you don't mind opening these authorization, and don't have any other way to get feedbacks, I think Flurry is really a choice to consider.
It's so simple to use and gives you lot of information.
Now I regret that it is not more flexible with the information it gives. I would like to have a way to create my own requests. For instance, I would like to know how the firmwares are distributed on the G1, but you don't have that much freedom ( even if there are a lot of information already available ).
A custom system done by myself would be more flexible. But, let's be honest : I won't invest time enough  to have something as elaborate as their tool ! ( not to mention the fact that I don't know how to present the data in such a nice way ).

Friday, January 1, 2010

Android Market : updating an application does not update its place in the 'by date' order anymore ?

Today, I updated my 'Word Prospector' Game, with a small bugfix.

I then took my phone, went to the market to see and test from the market the change.
And ... I couldn't find my game in the top of the list sorted by date !!!
It should have been there, at the first place. But, no, I tried to find it, but couldn't because it was sooo far in this list...

It could be a temporary, local bug, and maybe the old market behavior will come back in the next days / hours ( the famous bug of the 2K10 year ? )?
But I feel like it is a big change in the market policy !

What are the consequences of such a move :
* The "By Date" order in the market gets its real meaning back : you won't always see the same applications in this list. It's quite true that it was strange to see a very old application at the top of this list !
* Constantly improving / debugging your application will have a much lower impact on your visibility on the market. By now, every new version brings some new users. This could be the end of this behavior. Does it means people won't update / improve / debug their applications as much as before ?
* it certainly means it will be better to make a lot of application rather than having an application you constantly improving
* Perhaps it means you should make a 'top quality' appplication from day one, because improving it won't bring you any more users ( but that was already the case because of the ratings : if at start your ratings are low, it is very difficult to change them )
* Will people create a new application when they want to release a new version, or at least when a new feature is integrated in their version ?

Can we consider it is the market getting more mature ?
In my case, I hope that it's just a bug...

What do you think ?


EDIT : It looks like I've been alarmed by a well known ( but that I didn't know ) : an updated application will be at the top of the 'By Date' only if the previous update was more than 'some time' before ("some time" being around one week )
I didn't know that, and didn't observe this behavior before !

Thank to all the people that gave me this info !

Thursday, December 10, 2009

How to improve your application : a crash reporter to improve stability !

What is a crash reporter :
One condition for success, when shipping an application is that it should be as stable as possible !
And to achieve this, you have to test as deeply as possible your application. But :
1) If you are alone, it is a time consuming task,
2) It's still easy to miss some bugs, some special cases.

On the other hand, if you have a lot of users, the application will be launched a lot of times, a lot of special cases will be experimented by users, so they will suffer from unexpected crashes.

So we have to collect informations on the crashes that occurs on the end user device !
This is what a crash reporter is doing : whenever your application crash (and don't be over confident, it WILL happen ), it sends you informations on the conditions of the crash...
Don't get me wrong : the end users should not be your testers. If your application is full of bugs, they won't use it anymore, and you're just loosing your time ! But if you estimate your application is safe enough, and that you can't find any more bugs, publish your application with a crash reporter : you'll see all the 'other' bugs. The ones that result from situation you didn't imagine, and that really happen on your customer mobiles !

So what is in the crash reporter :
A crash is made of two different parts : the first one that collects information, and the second one that sends this information to you.

Note that there are already some packaged solutions for this, like this one :
http://code.google.com/p/android-remote-stacktrace/

But I preferred to create my own one (is it a good solution ? )

What to report ?

For each report, I chose to report as much information as possible. That includes :

  • The callstack
  • The information about the system that android provides me ( like the device type, the OS version, the application version, ...)
  • The avalaible memory ( memory is one of the most common issue in small mobile world ).


How to report ?
Most of the crash reporters I saw or heard about are using a Http connection silently opended to send the information to a Php wbe server that will store the issues in a database.

But I prefered to simply use mail :
Pro :

  • Simple to implement,
  • No server side code to make / test / debug,
  • gmail will automatic dealed with cases where there is no internet connection, 
  • Let the customer know that his problem is taken care of,
  • Let the customer know exactly what informations he sends you,
  • Involve the customer in the quality of your program ( if he didn't send a report, he can hardly complain the application is still bugged, so he 'should' be more tolerant).


Cons

  • All customer won't send the report !
  • Some customer could fake it ? That would be strange, but we definitively live in a strange world... So better be prepared to it !!
  • If you have a lot of bugs / a lot of customers, you can finally have a lot of mail to treat !
  • A Web/database solution is way easier to use for creating analysing tools ( like counting the occurences of each bugs, linking it to a version, etc ... )


So as you can see, each solution has some advantages. I would say if your application is big, go for a web/database solution.
In my case, I knew ( actually I hoped ) I hadn't a lot of bugs, so I chose the mail solution BECAUSE IT IS WAY SIMPLER TO DO !!

So here is the code :

Monday, December 7, 2009

Introducing the X Phone




Sorry...
Couldn't help...

(note that it is in German, but you don't need to understand what is said to fully appreciate this video )
- thanks Dom for this link !!

Monday, November 9, 2009

Google To Acquire AdMob

It is official : AdMob is now part of Google !

This is kind of funny :
Google is the giant Leader of virtual ads.
But on its own OS, its ads solution is still not ready ( it is still in beta state for the moment, and only avalaible for people with 100 000 pages views / day -sic- )...

So it just bough the current leader on mobiles !

Wednesday, October 28, 2009

Tutorial : How to customize your toasts !

Toast, despite their strange name that an average french developer like me doesn't understand, are really usefull in Android world ! Specially for developers !

The Standard Toast with butter :
It is obviously a easy and quick way to send text feedback to the user and the developer ( as it is a nice tool to give feedback ).
Toast.makeText(mCurActivity, text, Toast.LENGTH_SHORT).show();

This line has been used such a number of times in my programs, and in many others !


The Toast with orange jam :
But the standard 'look and feel' of your toast does not necessarily fit your application's look. And the default position can be a issue too.
In Word Prospector, I give feedback on whether the word is correct with a toast. But at first this toast was over my game board, preventing the player to click on some letters !

Then enters the toast customisation !!



Here's I changed the position of my toast, in order to have it in the right - bottom corner, and its background, so it's like the others gui element of the page !

To change the toast position, you have to use the setGravity Toast method. You have to provide the gravity of your toast ( this strange Android beast that indicates where your gui element will end up being ), and you can refine this position by some offsets.

To change the style of the toast is also easy :
The default toast is made of a textView.
You can actually access this textView, and change anything you want inside ( in my case, the background image, but you can imagine changing the font, or the text color / size, ... )

Here's my code to change the pos and the background :
protected void ToastMessage( CharSequence text, int ToastPos )
{
Toast toast = Toast.makeText(mCurActivity, text, Toast.LENGTH_SHORT);
View textView = toast.getView();
switch ( ToastPos )
{
case TOAST_LEFT_POS:
toast.setGravity( android.view.Gravity.BOTTOM | android.view.Gravity.LEFT, 00, 0);
break;
case TOAST_CENTER_POS:
toast.setGravity( android.view.Gravity.BOTTOM | android.view.Gravity.CENTER, 00, 0);
break;
case TOAST_RIGHT_POS:
toast.setGravity( android.view.Gravity.BOTTOM | android.view.Gravity.RIGHT, 00, 0);
break;
}
textView.setBackgroundResource( R.drawable.textview);
toast.show();
}


The toast with butter, and orange jam, ... and some chocolate...
Do you want more customisation ?
You can actually change the textView inside the toast by any view you want... Any view.... Including a custom view declared in a Xml file, and that you can inflate inside your toast...
In this case, you can easily add several lines, or images, or whatever you want !

No Toast with orange jam and chicken wings allowed !!
Despite being a really nice to tool to master, the toast is limited in two ways :
* You don't have control on the toast duration. Actually there are only two states LENGTH_LONG and LENGTH_SHORT. Note that I imagine you can force the closure of a toast with the cancel method ( I didn't test it )
* More important : you can't have several toasts at a time ! So if you need to display a new toast and old one is already present, the new one will be queued by the system, and wait for the old one to disappear. At some times, it can be a important limitation, as it means you can't control the feedback your giving will apppear at the correct moment !
You can try to close the first one in order to display the new one ( with the cancel method... Remember ? The one I didn't test ), but in some cases the two toasts should be at different places, and you want the user to see and have time to read both of them !

Monday, September 14, 2009

Tutorial : how to use the LED with Android phone

Some android phones have a nice LED ( but not all of them: I don't think the Galaxy has one ).

This LED can show lots of different colors, and adding this feature in your application is a nice little "plus" !
And it's really easy...


So how to do it ?

You just have to get the notification manager, and use it to send a flash Light notification.
The notification contains the color light, the duration with the LED On, and the duration with the LED off.

Here's the code :

private void RedFlashLight()
{
NotificationManager nm = ( NotificationManager ) getSystemService( NOTIFICATION_SERVICE );
Notification notif = new Notification();
notif.ledARGB = 0xFFff0000;
notif.flags = Notification.FLAG_SHOW_LIGHTS;
notif.ledOnMS = 100;
notif.ledOffMS = 100;
nm.notify(LED_NOTIFICATION_ID, notif);
}

LED_NOTIFICATION_ID is just a value to register this allocation. In my case, it is just 0.
Here, the LED will flash with the color given by the parameter ledARGB, in this case 0xFFff0000 (red ). It will be ON for 100 milliseconds, then OFF for 100 milliseconds, and then loop back ON, etc...

This is nice, but wait... How do I STOP it ?
This code will let the LED flash for ever !

To stop it, we have two solutions :
1) decide to only have the LED flash once, so in this case, you have to add the FLAG_ONLY_ALERT_ONCE flag :
notif.flags = Notification.FLAG_SHOW_LIGHTS | Notification.FLAG_ONLY_ALERT_ONCE;
2) Let the LED flash for as long as you want, then cancel the notification. In my case, I stop it after a certain amount of time by posting a message with a delay.
Here is the full code for this :

private void ClearLED()
{
NotificationManager nm = ( NotificationManager ) getSystemService( NOTIFICATION_SERVICE );
nm.cancel( LED_NOTIFICATION_ID );
}
private Runnable mClearLED_Task = new Runnable()
{
public void run()
{
synchronized( LetterGame.this)
{
ClearLED();
}
}
};

private void RedFlashLight()
{
NotificationManager nm = ( NotificationManager ) getSystemService( NOTIFICATION_SERVICE );
Notification notif = new Notification();
notif.ledARGB = 0xFFff0000;
notif.flags = Notification.FLAG_SHOW_LIGHTS;
notif.ledOnMS = 100;
notif.ledOffMS = 100;
nm.notify(LED_NOTIFICATION_ID, notif);
// Program the end of the light :
mCleanLedHandler.postDelayed(mClearLED_Task, LED_TIME_ON );
}


With mCleanLedHandler being just a handler, and LED_TIME_ON the flash duration.

Some notes :
1) Don't let a handler alive when changing activity ! On the OnPause of your activity, clear the handler :
mCleanLedHandler.removeCallbacks( mClearLED_Task );
2) programming the LED suffers one issue : the emulator does not emulate the LED at all... So you have to use a real device to see what it looks like ( but anyway, you always use real device at some point, don't you ? :) )
3) programming the LED then suffers a Second issue : when the real device is connected to your PC in order for you to develop / test / debug, the device is also charging the battery. And in this case, the LED is orange. Whatever notification you send ! So you have to test without your device plugged !


That's all !
With all that, you can code some nice LED flashes !!!

Thursday, August 20, 2009

Cupcake finally in France with Orange !


At last, it arrived !!

As you may already know from my previous article ( here ), here in France, the HTC Dream ( G1 ) was still running with a 1.1 version of Android.

And now an update to cupcake has finally been released by Orange !

So now, I have access to video recording, to the widgets, ... and above all, to all the applications that used the Cupcake API, and that we couldn't use till now ! ( it also means I will be able to try Python on my phone ! ).
That's not all !
This is a version coming from HTC, and in the package, we could also find :
* a PDF viewer ( it's nice to have a PDF viewer ! It's far from being uber performant, but, still it's here, and that a good point !!
* QuickOffice ( a office document reader : I tried it on several Word files, and it was OK, but for a big one - big being about 600 ko in my case, so not THAT big. Once again the performances are far from being stellar, but at least we can read some docs ! )
* A microsoft exchange mail reader and synchronization tool. Actually, I didn't tried it, but I'm sure a lot of people out there will be very happy with this !



But still I can't understand all that about this version :
* The installation is done via a PC, and we lose all of our data... That's not nice...
* We don't have any virtual keyboard ! That's a strange one... We still have the physical one, and I'm sure it is better. But in some cases, it's painful to open the physical keyboard just to type one word. And there are a lot of applications that don't deal that nicely with opening the keyboard...
* The last point is, in my opinion the worst of all : a lot of application are missing from the Android market.
To be sure to really understand what was happening, I created two void applications, one called OrangeTry, and the second called OrangeTry2.
I protected only the first one, and I released them ( for 5 minutes only )...
And guess what : only the second one appeared :

So this new version filters the protected applications !

I can't understand why they decided that ( actually, what I really think is that it is just a mistake ).
From what I understood from Google deal with the carriers, carriers are getting some money from the sell of Android applications !
As most paid applications are protected, this configuration will make Orange make less money than with the previous version ! How strange !

And I'm missing some free applications that were, for some reasons, protected ( this is a mistake : a protected version is about twice as big as the unprotected one ! ).
I'm particularly missing Dizzler !


A little question to finish this post :
Is there any carrier / country left where Cupcake has not been released ?

Tuesday, August 11, 2009

Tutorial : How to have two buttons on each side of the screen


Sometimes, things are quite confusing in the Android World.

I wanted to have one two buttons, on each side of the screen ( the famous next and previous buttons ).
I though it would be really easy to do.
I was wrong...

I had already done something quite similar for multiple column list view ( here ), but the trick I actually used for the multiple column view was not applicable here.
For the multiple column view, I could use a text view with a left aligned text, and another big text view taking all the remaining space with right aligned text.
As the textviews are invisibles ( only the text inside was visible ), the trick was unnoticable.


So now, my first try was to put my buttons in a linear layout, and have one with a right gravity, and another with a left gravity.
But it just failed : the two buttons were on the right, or the two buttons were on the left, but I couldn't obtain the result I wanted.

Finally, after some hours struggling with the documentations, some blogs, and the samples from the 1.1 SDK ( and my cat, but that is another story ), I found a way :


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<Button
android:id="@+id/button_previous"
android:layout_height="wrap_content"
android:layout_width="110px"
android:layout_alignParentLeft="true"
android:textSize="15sp"
android:typeface="monospace"
android:textColor="#ffFFffFF"
android:background="@drawable/textview"
android:text="@string/PreviousButton"
>
</Button>
<Button
android:id="@+id/button_next"
android:layout_height="wrap_content"
android:layout_width="110px"
android:layout_alignParentRight="true"
android:textSize="15sp"
android:typeface="monospace"
android:textColor="#ffFFffFF"
android:background="@drawable/textview"
android:text="@string/NextButton"
>
</Button>
</RelativeLayout>

So I discovered the alignParentRight / alignParentLeft tags !

What are the lessons learned :
* Android GUI is not always as easy as planned
* The linear Layout, that is often the first choice, is often the wrong choice. Relative Layout is much more flexible.
* Where are the API Demos samples in the 1.5 SDK ?

Sunday, August 2, 2009

Cupcake finally coming for orange in France ?

Here, in France, Orange have sold the HTC Dream ( the G1 ) for several months.

But the Android version that came with the Phone was the 1.1...
And no update came since google had released Cupcake.

So I was quite surprised when Google send developers a mail to explain that "deployment of Android 1.5 is nearly complete"...

It looks like my phone is one of the last without Cupcake.
But now it looks like an update is finally coming 'in early august'...

I really hope so !

Monday, June 29, 2009

A C programmer in Android world : discovering Java allocations

I'm mainly, in my day world, a C / C++ programmer.
So I'm used with this language and sometimes, I feel like I would prefer to use another, more modern language !

So I was quite happy to try Java. I saw Java as a kind of modern, cleaner C++, with some nice features, and a more improved object model.

But for the 'WordProspector' game, in the process of taking the database out, and replacing it with 'something' else, more lighter (see here ), I found a serious limitation of this Java object model...


The issue :
As 'WordProspector' is a word game, it is shipped with a word dictionary.
At first, I stored the dictionary as a SQL database, as it was simpler for me at that time.
but when I found how memory costly it is, I decided to switch to another way to store it.

So I came up with a kind of tree with a letter for each node.
In my effort to compress the dictionary as much as possible, I found a way to store each node as a 8 bits integer plus a 16 bits integer, so it was 3 bytes.
So I had a Node class, with simply a byte and a short.
But I had a big number of node instances in my tree : something like 300 000 nodes.

So when I loaded the tree, I started by allocating all the nodes.
Allocating the nodes with java was something like :




NodeArray = new Node[nbNodes];

for ( int i = 0; i <>
NodeArray[i] = new Node();




When I tried it on the emulator... It took several minutes... Then the program crashed...
Too much allocations of this size !

I was amazed that I just couldn't easily create this really simple array !!

Then I discovered that allocations of simple type arrays didn't demand to allocate each element !

So finally, I get rid of my nice Node class, save the whole thing as a byte array, and interpret, on the fly, the byte array as a byte plus a short.
With that, the allocation is as fast as it could be :


NodeArray = new byte[nbNodes * 3];



Conclusion:
With this allocation issue, I had to get rid of my Node class, I just have a big array of bytes.
Acces to member are much more complicated, the code that was once so simple is now much more complex.
Add on this issue the fact that what I really wanted was an unsigned short, and not a short.
My code is now full of bits manipulation to create and interpret some bytes as unsigned short, or as bytes, depending of the situation.


With C/C++, I would never have this problem, and I would have a clean object code !

Saturday, June 20, 2009

Tutorial : How to access a Android database by using a command line.

When your start dealing with a database in your program, it is really important and useful to be able to access it directly, outside your program, to check what the program has just done, and to debug.

And it is important also on Android.

Here is how to do that :

1) Launch the emulator (or connect your real device to your PC ). I usually launch one of my program from Eclipse for this.
2) Launch a command prompt in the android tools directory.
3) type adb shell.
This will launch an unix shell on your emulator / connected device.
4) go to the directory where your database is :
cd data/data
here you have the list of all the applications on your device
Go in your application directory ( beware, Unix is case sensitive !! )
cd com.alocaly.LetterGame
and descend in your databases directory :
cd databases
Here you can find all your databases. In my case, there is only one ( now ) : SCORE_DB
5) Launch sqlite on the database you want to check / change :
sqlite3 SCORE_DB
From here, you can check what tables are present :
.tables
6) enter any SQL instruction you want :
select * from Score;

This is quite simple, but every time I need it, I don't know where to find it.

Tuesday, June 9, 2009

Developing with Python on Android : a dream comes true ?

Google still innovates and offers as many tools as they can to us developers !

I'm a big fan of Python, and several times, I wanted to try things on my phone, but without having a development PC around me.
Now they combines both of my implicit wishes, and we can develop in python (and LUA, BeanShell, and other langages to come ) from the mobile !!
I'm really excited about that.

Next question is :
Could I embed my python script that I developed on my phone in a real Java application ?
That would be awesome : it would mean I really could develop parts of my application from my phone, or even have a scripted part of my application that could evolve without a PC !!!

Waaaa...

Thank you, Mr google !!!


Google Open Source Blog: Introducing Android Scripting Environment

Edit :
Sadly, this application is for Cupcake only, so I can't use it...
I will have to wait for Cupcake to be avalaible on my phone.
That makes me wonder how many phone are still stuck with the 1.1 OS ?
From what I've read, in the US, the update is finished, so there should be no 1.1 left.
But outside US ???