2010
01.17

When writing Android applications that heavily rely on networking it can sometimes be useful to inspect the network traffic going out and coming into your device. Especially when writing applications that implement networking protocols (like ftp, smtp, ssh, xmpp,..) the ability to inspect packets at TCP-level is invaluable. The following guide will show you how to do this on your Android device.

Prerequisites

What you’ll need in order to do this:

Procedure

First you have to get the tcpdump binary on the device. Execute the following adb push command in a terminal from your SDK/tools directory.

$ ./adb push /home/steven/tcpdump-arm /data/local/

Next login to your device by executing the following adb shell command

$ ./adb shell

Once logged in to your device, navigate to the directory where you stored the tcpdump binary and gain root privileges.

$ cd data/local
$ su

Start the tcpdump binary and save the captured packets to file.

$ tcpdump-arm -s 0 -w out.txt

The -s 0 argument is necessary to capture the whole packets instead of only the first x bytes. The -w out.txt writes the captured packets to file.

Every network packet that passes your network connection will be recorded in the file out.txt. Now you can launch the app you’re debugging (if it isn’t already running) and use the functionality of which you want to inspect the network traffic.

When you think you have captured enough network traffic for analysis, press Ctrl+C in your terminal to stop the tcp-dump process.

Copy the resulting file out.txt back to your computer by exiting the device shell and executing the adb pull command.

$ exit
$ ./adb pull /data/local/out.txt /home/steven/out.txt

That’s it, you’re done. Now you can either analyse the network traffic by hand by reading the file with your favorite text editor or you can load it into a network analysis tool like Wireshark.

Happy debugging!

2009
08.13

I’ve had this idea for a new Android app in my head for a few days and today I finally took some time to implement it.

Quickcopy

Quickcopy is a little application that lets you store little text snippets that you otherwise need to type frequently. For example an email address or a difficult to type login to some website.

Whenever you need one of these snippets in an application you just launch Quickcopy, tap the snippet you need and go back to your application where you can then paste the snippet.

quickcopy1quickcopy2

The first version of Quickcopy is already available on the market. The features currently implemented are

  • Add entries via the menu button
  • Copy entry to clipboard by tapping
  • Edit entry by long pressing
  • Delete entry by long pressing
  • View instructions via the menu button

To download this application use the barcode scanner app to scan the following barcode or manually browse to Quickcopy in the Android Market app.

quickcopybar

2009
08.10

As a new feature in my Android app for the belgian mobile phone carrier Mobile Vikings I wanted to display contact names in the call history instead of the number. After a bit of googling I found that Android provides a few contentproviders holding the data I needed (contact names and phone numbers).

In a first attempt I retrieved all contacts with a simple query on the Contacts.Phone contentprovider, manually looped over the results and compared each number with the number I wanted to replace. This worked only when the number on the phone was stored exactly in the same format as the Mobile Vikings API returned it. If the Mobile Vikings api returned the number with the area code and the number was stored on the phone without the area code, no match was found.

After trying to figure out how I could get both numbers in the same format for comparison I figured out that Android provided a mechanism to do this. I could encode a phone number string into a filter URI provided by the Contacts.Phones contentprovider and use that filter to query on.

The code looks like this

	private String getContactNameFromNumber(String number) {
		// define the columns I want the query to return
		String[] projection = new String[] {
				Contacts.Phones.DISPLAY_NAME,
				Contacts.Phones.NUMBER };
 
		// encode the phone number and build the filter URI
		Uri contactUri = Uri.withAppendedPath(Contacts.Phones.CONTENT_FILTER_URL, Uri.encode(number));
 
		// query time
		Cursor c = getContentResolver().query(contactUri, projection, null,
				null, null);
 
		// if the query returns 1 or more results
		// return the first result
		if (c.moveToFirst()) {
			String name = c.getString(c
					.getColumnIndex(Contacts.Phones.DISPLAY_NAME));
			return name;
		}
 
		// return the original number if no match was found
		return number;
	}

The result looks like this

contactnames

2009
08.07

In one of my previous posts about Google Wave I mentioned a security issue concerning gadgets. I decided to do a little more research on this subject and to do some experiments on some publicly available gadgets.

The issue

Before I get into detail about the issue, you need to know how Google Wave gadgets work. Gadgets are little pieces of html/javascript code that run inside of a wave. The state of a gadget is shared among every participant of the wave. Developers can access variables in this state by executing wave.getState().get(’name_of_variable’). To save or update variables into the state the following function exists. wave.getState().submitDelta({’name_of_variable’: value})

This last function is what bothers me about the way gadgets work. The wave server accepts any data that submitDelta passes on without any form of validation or authentication. As javascript is a clientside interpreted language that is executed inside of the browser, any user with the firebug extension (or some other debugging app/plugin) can alter that running code and manipulate these gadgets in ways that are not intended.

Some examples

Google Wave developer sandbox users should be familiar with the public “Wanna buy a wave t-shirt” wave. It is a wave to show of a simple counter gadget that is explained in the gadget api docs. the gadget has a button and a label. Every time the button is clicked the value in the label is incremented by one. The code for handling the buttonclick looks like this.

function buttonClicked() {
     var value = parseInt(wave.getState().get('count', '0'));
     wave.getState().submitDelta({'count': value + 1});
}

As you can see the code retrieves the current value of ‘count’ from the state, stores it in a variable ‘value’ and then pushes that value+1 back to the server with submitDelta(). To try something out I placed a breakpoint just before the new value was submitted back to the server, changed the value of ‘value’ to ‘99999′ and hit continue. As expected, the wave server accepted my altered value and pushed the new state to every user currently participating that wave.

In the gadget api docs there is an auction gadget available which demonstrates the use of wave participants. This gadget lets wave participants place bids. The gadget displays the name and picture of the highest bidder together with the amount. The bidding code looks like this.

    function buttonClicked() {
      var viewerId = wave.getViewer().getId();
      var state = wave.getState();
      var bid = parseInt(document.getElementById('yourBid').value);
      var currentBid = parseInt(state.get(viewerId, '0'));
      if (bid > currentBid) {
        delta = {};
        delta[viewerId] = bid;
        state.submitDelta(delta);
      }
    }

What I did here was : I placed a breakpoint after the viewerId was retrieved from the wave. This viewerId held my email address because I was the one viewing the gadget. I replaced this email-address with an address from another participant inside that wave and changed the value of the bid variable to 99999. After I hit continue to execute the remaining code, again my changes were accepted by the server and I had successfully placed a bid in someone else’s name. The updated state was once again pushed onto every wave participant’s gadget and they all got to see my victim’s name, picture and 99999 bidding amount.

Solution

What can developers do to prevent this abuse? Well, as long as we have no control over how the waveserver validates input received from submitDelta() there is not much we can do. If you really need serverside validation for your gadget then I think it is best to avoid the shared state and use an external server (on which you can execute validation code) to store your shared variables.

Some people may not consider this to be a security issue and will say it is just the way wave gadgets are supposed to work. As this may be true, I hope it’s not because this would limit the use of gadgets to fairly trivial appliances.

I have no idea what the Google Wave developers think of this. I am surely going to report this issue in the Google Wave issue tracker and keep you updated on the status.

2009
08.06

This is part 3 of my Google Wave developer sandbox review. Part 1 and part 2 were both an introduction to some wave concepts. In this part we’ll be taking a look at the Google Wave Gadget API by writing a simple rating gadget. If you don’t know what gadgets are or what they do, please read my previous post here.

The gadget

The gadget I have written is supposed to be used when you want wave participants to rate something inside your wave (or the wave itself). You simply add the gadget to your wave, set the title and you’re good to go. I also wanted the gadget to have a settings menu that was only visible by the person who added the gadget to the wave.

Rate me gadget

The full source of this gadget is available at http://vbsteven.be/wave/rateme.xml . You can add the gadget to one of your own waves by choosing extensions/add gadget in the debug menu and then pasting the url into the textbox at the bottom of the dialog.

Basic code layout

Google Wave Gadgets are simple xml files with html and javascript code embedded in them.

As you can see here, this is just an xml file with some wave-specific settings at the top like the title and the height of the gadget. Then there is a CDATA block which contains all our code. At the top of this block you’ll find the javascript includes (for JSON) and some javascript functions which define the behaviour of the gadget. At the bottom of the CDATA block you’ll find the HTML code which define the UI elements. When you want to use other external files (CSS for example), I suggest you link to those files at the top of the CDATA block.

Initializing

To initialize our gadget we need to define and register an init() method. This method will be called every time the gadget is loaded (for example when someone opens the wave). This method does not need to be long and is basically just to register the necessary callbackfunctions. In this case just the callback function that will notify our gadget when the shared state has changed is registered. As the shared state may not be fully initialized it is important that you don’t access the state object in this function.

function init() {
	if (wave) {
		wave.setStateCallback(stateUpdated);
	}
}
gadgets.util.registerOnLoadHandler(init);

Manipulating state

It is very important to notice that the state of a gadget is shared among all participants of the wave. When one user performs an action that changes the state, this change will be reflected on every instance of that gadget that is currently open. The state of a gadget is represented by a state object that contains key-value pairs. Values can be retrieved from the state with the get(’key’) function and stored in the state with the submitDelta({’key’ : value, ‘key’ : value}) function.

Currently the state object only works with strings. So it is not possible to store complex objects or arrays into the state. When you do need to store a complex object or an array, you can convert the object to a JSON string for storage and parse that string back to an object after you get it back from the state. The documentation on the get and submitDelta functions wasn’t very clear to me at first, which led to me losing some time trying to figure out why my array of voters wouldn’t show up into the state object (thank you firebug).

You can find some examples of how to manipulate the shared state in my vote() and saveSettings() functions.

		function vote() {
			var total = parseInt(wave.getState().get('total', '0'));
			var count = parseInt(wave.getState().get('count', '0'));
			var voters = JSON.parse(wave.getState().get('voters', '[]'));
 
			total += parseInt(document.getElementById("rating").value)
			count++;
			var rating = total/count;
			rating = rating.toFixed(1);
			voters.push(wave.getViewer().getId());	
 
			var voters_json = JSON.stringify(voters);	
 
			wave.getState().submitDelta({'count': count, 'total': total, 'rating': rating, 'voters': voters_json });
		}

Reacting to a state-change

If you registered your callbacks well, your stateUpdated() function should be called whenever a value changed in the shared state. This is the ideal time to get the updated state and update the UI. Most of the UI-related code should be in this function. Every time this function is called in my rateme gadget, I fetch the latest values from the state and update every UI component.

		function stateUpdated() {
			var p_rating = document.getElementById("p_rating");
 
			if (!wave.getState().get('count')) {
				p_rating.innerHTML = "rating : n/a";
				p_rating.innerHTML += "
votes : 0";
			}
			else {
				p_rating.innerHTML = "rating : " + wave.getState().get('rating');
				p_rating.innerHTML += "
votes : " + wave.getState().get('count');
			}
 
			var title = document.getElementById("h2_title");
			title.innerHTML = wave.getState().get('title', 'Rate me');
 
			if (wave.getViewer() == wave.getHost()) {
				// current user is the user that added the gadget to this wave
				var p = document.getElementById("p_settings");
				p.innerHTML = '<a onclick="showSettings()" href="#">show settings</a>';
			}
		}

Getting user info

Because I wanted to make a difference between the original user who added the gadget to the wave and the other users I had to have access to some user info. Luckily the wave object supplied by the API provides us with the necessary data. The wave.getParticipants() function returns an array of all users in the wave, getViewer returns the current viewer and getHost() returns the user that added the gadget to the wave. As you can see in the above code snippet, a simple equals check is enough to authenticate the user as the host and display the link tag.

Security

Ofcourse the authentication above looks easy. and it is,  but it also is a security issue. When you ask  any security specialist about AJAX apps, there is a very big chance he will say “Never trust the client”. And he’s right, as all javascript code is run inside the browser, any kid with firebug installed can alter that code. If you take a look at the above code sample you’ll see that a simple if (true) is enough to gain access to the settings panel.

In a normal webapp this wouldn’t be such a big deal. Even when the malicious user gained access to the settings by messing with the clientcode, the server would still verify every action and will prevent unauthorised actions. Because our gadget runs inside of google wave and we have no control over the code running on google servers, there is no way to validate the user input on the server and therefore the server will accept this user input.

Because of this issue there is no real safe way to handle authentication so every user inside the wave is able to alter the shared state, making gadgets unsafe for sensitive data.

My opinion

In my opinion the Google Wave gadget api is very easy to work with. As gadgets are written in plain html/javascript, webdevelopers with some experience in js won’t have a problem writing apps for the Google Wave platform. Although I’m still rather new to javascript (only did some basic ajax programming in school), I didn’t have any major problems while writing this little gadget. The api itself is very simple, straightforward and well-documented with very good code-samples.

On the downside, because of the security issue mentioned above I’m afraid gadgets will be limited to simple apps handling unimportant data (games, polls, etc).

2009
08.03

As the first part of my Google Wave developer sandbox review handled basic usage, this article will be about some more advanced features currently in the Google Wave developer sandbox.

Attachments

Adding attachments to a wave works similar to adding attachments to an email. You click the attachment button, select one or more files, click submit and voila. Your files are uploaded to and displayed inside the wave.

You can attach almost any filetype to a wave. In case you have one or more image files the thumbnails of these files are displayed inside the wave. When you click on a thumbnail the larger image is displayed on a dark background. When there are multiple images added to a wave you can browse them by clicking the image button at the bottom and selecting “view as slideshow”.

Screenshot-Steven (12+) - Google Wave - Google Chrome

Screenshot-Steven (12+) - Google Wave - Google Chrome-1

When uploading other filetypes, Google Wave tries to guess the contenttype of each file in order to display a fitting icon. This feature seems to work quite well. When I added a .air installation file to a wave, the wave correctly identified that file as a zip file and displayed the corresponding icon. After adding a pdf file, a preview of the file is displayed. Clicking on a non-image file in a wave results in a download of that file.

When you have the correct Gears plugin for your browser you should be able to add attachments to your waves by dragging them from your desktop into the wave. Because this feature didn’t work out of the box with any of my browsers and I didn’t have the time to figure it out, I decided to leave it for now.

Gadgets

Gadgets are small pieces of software you can attach to a wave to add custom functionality. Gadgets can be as simple as a counter that counts how many times a button has been clicked or as advanced as an embedded map displaying the current GPS location of the wave users.

Screenshot-Steven (14+) - Google Wave - Google Chrome

The state of a gadget is shared among wave users. This means that when one user changes the state of a gadget, this change is visible for every user inside that wave. For example when a Google Maps gadget is embedded in a wave and user A is zooming in on a city, the gadget on user B’s screen will synchronize and automatically zoom in on the same city.

Adding a gadget is currently done via the debug menu. After choosing debug/extensions/add gadget, a popup is displayed with a list of available gadgets and a textbox to add custom gadgets stored somewhere on the net. Google Wave has a few default gadgets including : a chess game, a Google Maps gadget and a “Who is coming” gadget. At the moment I have not tried to add some custom gadgets yet, I will see how that goes when I write my first own gadget one of these days.

Robots

Robots are a little different from gadgets. A robot is an automated participant in a wave that interacts with it just like normal users do. You add a robot to a wave just like you would add another user. First you add the robot to your list of contacts (each robot is identified with a unique email address) and then you start a new wave with it or add it to an existing wave. Robot’s can interact with wave users in all sorts of ways. They can reply to messages, edit messages, create waves, add attachments, …

There are already quite a few nice robots out there for use with the Google Wave developer sandbox. Some of them are developed by the Google developers (Tweety – a twitter robot, Spelly – a spell checker, Stocky – a stock checker) and some other robots are developed by external developers (Roshambo – a rock paper scissors robot – is currently the only non-google robot I have tried).

Debug menu

The Google Wave developer sandbox comes with a handy debug menu. This menu has items for developers to check network information, check wave information, set the window size (not working in chrome dev build here atm) and generate/throw exceptions for debugging. At the moment also non-developers need this menu as it is the only way (that I know of) to add gadgets to a wave.

I haven’t had the chance to check out how most of these functions work as I haven’t written any code for the platform yet. Once I start developing a gadget and a robot I will probably use this menu a little bit more and have something more to say about it.

Wrapping up again

This post wasn’t very long as it was just an introduction to a few advanced concepts in the Google Wave developer sandbox. Today I will start experimenting with the gadget and robot API and in the next post I will talk about my experiences developing for the Google Wave platform. I already have an idea for the gadget but still no clue for the robot. Idea’s are suggestions are still welcome. You can leave a comment or contact me via my new contact page.

2009
07.30

Two days after I got my invitation to the Ubuntu One beta and wrote a review about my first impressions I also received an invitation to join the Google Wave developer sandbox. As my Ubuntu One review was pretty popular (had about 1k pageviews the first day – thank you dzone.com users), I decided to write something down about my first Google Wave experiences.

I will divide the review into a few separate pieces. This post will handle the general Google Wave Sandbox stuff, things I noticed when I first used the service. A second post will handle some advanced wave concepts like search, attachments, gadgets and robots. The last two posts will be about writing a gadget and a robot on the platform. I still have no idea about what kind of gadget and robot to write so if you have any suggestions/ideas, please leave a comment at the bottom of this page.

What is Google Wave

For those of you who are not familiar with Google Wave I suggest you check out wave.google.com and watch the awesome introduction as it was presented at Google I/O 2009. Basically Google Wave is a commuication and collaboration tool for the web that tries to combine emailing, instant messaging and online collaboration into waves. A wave is a kind of thread t that is shared among users. Participants in a wave can leave messages for eachother, chat IM style, share photos, play games and add gadgets (like google maps for example) or robots (automated participants with a certain functionality).

Registration

After receiving my invitation to join the Google Wave developer sandbox I had to fill out a registration form with some personal data as well as three possible nicknames. Minutes after submitting I received another email stating that my account was created with my desired username (stevenvb) along with a second account for testing purposes (stevenvb-test). The addition of a second account for testing was a nice surprise as I didn’t have any friends with accounts on the sandbox.

First login

Full of excitement I clicked the link that would take me to the sandbox. Instead of the login screen I was greeted with a simple html document that stated that Google Wave did not support my current browser (I was using Firefox 3.5.1 on a 64bit ubuntu 9.04 box). At the moment Google suggests you use either Chrome/Chromium, FF 3.0+ or safari 3.0 so I launched my Chrome development build for linux and logged in succesfully.

wave-overview

After the first login on both accounts I was forced to change my password to something else than the auto-generated one from the invitation and finally I got to see some Google Wave action. The first thing I did was add my test account to my contacts and ofcourse opened a wave and added my test account. I opened a second browser window to login with my test account and started sending some basic messages back and forth in the wave.

Basic usage

Simple wave editing is pretty intuitive. When you start a new wave or click the edit or reply button on an existing message the editor pops up and you are presented with a few buttons to format your text. Because I had two browser windows open side by side I could see the “update-as-you-type” feature in action. When I was typing something on one account, the other account could follow what was being typed almost character by character. In my case the message was updated about once every 2 letters. The following two screenshots demo this behaviour.

Wave Text Editor

Update-as-you-type

As you can see there is a Draft button available in the editor to disable this behaviour so the other participants can’t see your progress unless you click done. Unfortunately this feature is not implemented yet so the draft button is disabled.

The next morning when I logged in again I was surprised to find my inbox to be full of messages. I found this odd because except for my test account I had noone else in my contact list. After checking out some of the messages it became clear that there was an email adress (wave-discuss@wavesandbox.com) which you could add to any wave you made to make that wave publicly available to all sandbox users.

It bothered me at first to have so many unread waves all jumping up to the top of my inbox every time someone else added a new reply so I quickly discovered a solution. I added a new search with the following query “-with:wave-discuss@wavesandbox.com”. This query would give me all waves in my inbox except those with the previously mentioned public email address. By doing this I also discovered that besides the wave-discuss adress there also were some other public addresses. There also were some public waves – created by the Google devs themselves – which had info about the latest changes and bugs in them.

When checking out these “patch notes” I tried out the playback function which works really nice. This feature helped me a lot in understanding the discussions that went on. The playback function takes a wave and presents you every change step by step in chronological order. This makes browsing a wave with over 100 participants and as much replies as easy as following a thread on a discussion board.

When something goes wrong…

When something goes wrong, and it will because the software is still in its alpha stages, a textbox pops up on top of the screen in which you can type what you were doing to notify the Google devs that something has failed and they can try to fix it.

whensomethinggoeswrong

When a wave itself has some issues it will notify you with a popup on top of the wave that mentions the wave in particular has some turbulence and it advises you to reopen that wave.

In the few days that I have used Google Wave it has never made my browser crash, which is a very good thing. I had a few “Dr. wave” popups notifying me there was something wrong internally but everytime one popped up I gladly filled out the form to notify the devs about what I was doing.

Wrapping up

All in all I had a very good first experience in the Google Wave developer sandbox. There are still some features to be implemented, bugs to fix and performance tweaks to be done but it definitely looks promising. I can’t wait to start developing some gadgets and robots for this platform, which I will blog about soon so be sure to check this site for updates every now and then. My next post will be about some more advanced features of the Google Wave developer sandbox like the usage of attachments, gadgets, robots and the debug menu.

I hope some of you found this post helpful or informative. If you have any comments about this post or have suggestions about the upcoming ones please leave a comment.

2009
07.28

Yesterday I received my invitation to the Ubuntu One beta. I played around with it for a few hours and these are my opinions.

What is Ubuntu One

For those who haven’t heard of it, Ubuntu One is a service that provides remote storage that integrates well with the Ubuntu Desktop. It allows you to share files between multiple machines, access these files from anywhere in the world via a webbased client and share your files with other Ubuntu One users.

Installation

After receiving my beta invitation I was asked to choose a subscription plan. Ubuntu one provides a free subscription plan with 2gb of storage and a paid plan with 10gb of storage for $10.00/month. I chose the free plan for now. After finishing my subscription I was presented some very clear installation instructions. Installation of Ubuntu One is as simple as installing 2 software packages. First the PPA which enables the Ubuntu One repository, then the actual software from the new repository.

When I first ran the software I was redirected to the website of Ubuntu One where I could login and add my computer to my Ubuntu One account.

Usage

Using Ubuntu One is as simple as just copying or creating files in the newly created folder on your harddrive.

Screenshot-Ubuntu One - File Browser

When you add or change files to the local Ubuntu One folder an icon in the notification area starts rotating to let you know the software is synchronizing the local and remote filesystems.

Screenshot_notification_area

After synchronization your files are available from the webclient. In this webclient you can view and download your current directories and files as well as upload new files or create subdirectories. You can share files with other Ubuntu One users by selecting a file, clicking on the “sharing” button and entering the email address of the person you want to share the file with.

The “Shared with me” folder contains the files or folders from other Ubuntu One users that have shared something with you.

Screenshot-Ubuntu One : File Sharing - Shiretoko

When you have another computer linked to your Ubuntu One account the new or updated files will be visible after you trigger a sync from thet computer.

Issues

When I added some files via the web client and wanted to sync the remote and local folders I couldn’t find a synchronize button. I had to manually click the disconnect and reconnect buttons on the notification icon for the synchronization to kick in. Another way to start the synchronization from remote to local was by adding or changing a file in the local folder. This would trigger a synchronization and the new remote files became visible locally.

When adding new folders in the webclient it looks like the folder is added twice. This looks like a minor UI problem because after a refresh or a sync the folder is displayed only once as it is supposed to be.

The web client doesn’t have a refresh button, after a sync you have to manually refresh the page to view the new state of the remote folders

My thoughts

Although Ubuntu One is still in its beta stage and still has some issues I am certain this will become a very userfriendly way of sharing documents between multiple machines or among friends. Because of it’s tight integration with the Ubuntu desktop and thus synchronizing files is as easy as copying or saving them to a folder, even the most non-technical users are able to share data without the use of USB-sticks or email attachments.

I encourage everyone interested in the service to apply for the beta and to start reporting issues so this project can get out of beta soon and become another valuable addition to the Ubuntu desktop.

2009
07.24

When writing the Call history part of my Android app for Mobile Vikings I wanted to display the calls and messages in a listview with their number, date/time and cost. Displaying some text values in a ListView isn’t that hard. I could use an ArrayAdapter object that takes an ArrayList of Calls and it would display the toString() method of each call in the ListView. What I wanted to do was display various attributes of each Call object (number, date, cost) in the ListView. Like this.

Calllog example

After some research I found out that the SimpleAdapter class was the way to go. The documentation on the SimpleAdapter class wasn’t very clear to me at first and there wasn’t a working codesample to demo the class so I decided to share my own code.

The SimpleAdapter class uses an ArrayList of Maps to hold the data. Each Map in the list represents the data for one row in the ListView. The mapping between the values in the Map and the TextViews in the layout file is done with two arrays. An array of Strings for the keys that need to be mapped and an array of ints with the id’s of the TextViews the data belongs to.

We’ll start of with the code for the layout file. This file describes how one row in our listview should look. I used a TableLayout because I wanted two columns. In the first column I have a LinearLayout with two TextViews. One for the number display and one for the time. The other column holds one TextView for the cost of the call.

<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="fill_parent" android:layout_height="wrap_content"
	android:stretchColumns="0" android:padding="5dp">
 
	<TableRow android:padding="5dp">
		<LinearLayout android:orientation="vertical">
			<TextView android:textSize="16sp" android:textStyle="bold"
				android:id="@+id/callog_detail_sms_number" android:layout_width="fill_parent"
				android:layout_height="wrap_content">
			</TextView>
 
			<TextView android:textSize="16sp" android:id="@+id/callog_detail_sms_date"
				android:layout_width="fill_parent" android:layout_height="wrap_content">
			</TextView>
		</LinearLayout>
 
		<TextView android:textSize="16sp" android:id="@+id/callog_detail_sms_price"
			android:layout_width="wrap_content" android:layout_height="fill_parent"></TextView>
	</TableRow>
</TableLayout>

Next up is the java code I used to convert my List of Calls to the List of Maps and to map the correct values to the corresponding TextViews.

// get messages from service
ArrayList<Call> smsMessages = service.getSmsCalls();
 
// initialize the List of Maps
List<Map<String, String>> list = new ArrayList<Map<String, String>>();
 
// iterate over all messages 
// create a map for each message
// fill the map with data
for (Call c: smsMessages) {
	Map<String, String> map = new HashMap<String, String>();
	map.put("number", c.getTo());
	map.put("date", DateParser.getTimeString(c.getBegin()));
	map.put("price", "€ " + c.getPrice());
	list.add(map);
}
 
// the from array specifies which keys from the map 
// we want to view in our ListView
String[] from = {"number", "date", "price"};
 
// the to array specifies the TextViews from the xml layout
// on which we want to display the values defined in the from array
int[] to = {R.id.callog_detail_sms_number, R.id.callog_detail_sms_date, R.id.callog_detail_sms_price};
 
// get a reference to the ListView
ListView lv = (ListView)findViewById(R.id.callog_detail_listview);
 
// create the adapter and assign it to the listview
SimpleAdapter adapter = new SimpleAdapter(this.getApplicationContext(), list, R.layout.callog_detail_sms, from, to);
lv.setAdapter(adapter);

That’s it. I hope this was helpfull to some of you. Improvements and comments are always welcome.

2009
07.22

Recently I bought myself a HTC Magic phone running the Android OS. After losing about € 10-15 on mobile internet connections in the first two days I decided to switch to the new mobile service provider Mobile Vikings. They offer 1000 sms messages and 1gb data transfer for € 15/month. The first thing I noticed after the switch was the fact that there was no easy way to know how much credits/messages/data I had left on my account. I either had to call a free number or log in to the Mobile Vikings website to check my balance.

They had a public API available but nobody had written an android implementation so I decided to write one myself. After all it’s a nice way to introduce myself to the Android API. I started up eclipse, downloaded the SDK, read a little about the API and started coding. In about 40minutes I had a very basic app that downloaded the JSON data from the Mobile Vikings API, parsed it and put the remaining balance on the screen.

The days after that I implemented a settings menu where users could enter their credentials and enable/disable the auto-update feature of the app. As an introduction to Android appwidgets I tried to get a little widget working that showed the remaining credits/messages/data on the screen and I succeeded.

A few days ago I cleaned the code a little and put an early version of the app available for the users to test at http://code.google.com/p/mobilevikingsandroid/ . Please use the Issue tracker  when you encounter any issues or have feedback.

I am currently working on the next feature (the call history) so be sure to check back every once in a while for updates.