Search This Blog

13 May 2012

LMDE upgrade broke me

While not strictly an Android issue, I figured I would post this here since it it related to my previously mentioned build box.

Every time LMDE upgrades, it breaks my packages.  At the very least, it breaks the nvidia driver (I should probably switch to source-based) but this last time, it auto uninstalled almost everything NVidia/Gnome/X11/Mate/Cinnamon/etc.

You might be asking, "Why didn't you read the release notes that said to not upgrade yet?"

Well, to be honest, I was used to the simplicity of the daily non-breaking Ubuntu updates.  This new Debian tri-weekly breaking rolling update is a new beast to get used to. I put rolling update in italic because I don't understand how updating more packages less often is 'rolling'.

Anyways, I spent the last 3 days trying to fix my box.  It would boot fine - into a command line... but I really prefer to use Firefox/Chrome/Opera over links to do my web searching.

I am sure these instructions are not the correct way to do things.  You might even say it could be a bad idea to get into someones head.  But since that was the only thing I tried that allowed me to start booting into X again, here we go.

STEP 1:
First off, I determined something I thought should be installed.  I don't know what the process in my head is for that, other than just selecting some random names I thought out to be there.  For example 'gdm'.

STEP 2:
Secondly, I need to find out the correct name for it. IE: it's 'gdm3' not 'gdm'. For this, I did:
apt search gdm
and found the one that looked right (in this case 'gdm3').

STEP 3:
Then, I needed to know whether it was actually installed yet or not -- and at which revision. For that I did:
apt-cache policy gdm3

This showed that it was not currently installed!  Well that doesn't sound right.

STEP 4:
If step 3 showed the correct (updated) candidate:
apt install gdm3
If it fails to install, it might list some unmet dependency.  If so, I start over at STEP 3 (or STEP 2 if that doesn't work) using that dependency as the new name to test.

STEP 5:
If STEP 3 showed the wrong (or no) candidate, I looked at the numbers [priority] at the beginning of each URL.  It might show something like '500' for the one you want to install but '*** 700' for the one you have installed or it wants installed.  Look at the URL for the one with the higher unwanted priority.

STEP 6:
edit /etc/apt/sources.list and comment out the URL from STEP 5
apt update
apt dist-upgrade
Go to STEP 3.


Like I said, these are probably not a good template to follow; however, next time instead of wasting 3 days trying to fix an upgrade I'll start here.

14 April 2012

Unknown Host Exception

I spent an hour trying to figure out why my little 1-page test app was throwing an UnknownHostException but the browser was working. To make matters worse, there was a red herring in that I could not ping from adb shell.

Turns out it was a stupid ommission. If you are having this problem, don't forget to add this to your AndroidManifest:

<uses-permission android:name="android.permission.INTERNET"/>

20 March 2012

Modify the emulator system.img

As a followup to my earlier post, I thought I would show how to modify the system.img the emulator is using.

This assumes that you are building Android and want to use some of the generated APKs inside the stock emulator.  That isn't the only scenario, but it's the one I am covering here.

First, we need to determine where the images are being loaded from. In this case my AVD is called "ics".
malachi@onyx:~/work/redo_system$ grep sysdir ~/.android/avd/ics.avd/config.ini
image.sysdir.2=system-images/android-14/armeabi-v7a/
image.sysdir.1=add-ons/addon-google_apis-google_inc_-14/images/armeabi-v7a/

Let's grab a local copy of those files
malachi@onyx:~/work/redo_system$ cp $ANDROID_HOME/add-ons/addon-google_apis-google_inc_-14/images/armeabi-v7a/* .

This time around, we are interested in the system.img. Let's unpack it.
malachi@onyx:~/work/redo_system$ mkdir unpacked
malachi@onyx:~/work/redo_system$ cd unpacked/
malachi@onyx:~/work/redo_system/unpacked$ unyaffs ../system.img
end of image

Replace a couple apps
malachi@onyx:~/work/redo_system/unpacked$ cd app
malachi@onyx:~/work/redo_system/unpacked/app$ cp ~/work/ics/out/target/product/SomeProduct/system/app/Contacts.apk .
malachi@onyx:~/work/redo_system/unpacked/app$ cp ~/work/ics/out/target/product/SomeProduct/system/app/ContactsProvider.apk .

Repack the system.img
malachi@onyx:~/work/redo_system/unpacked/app$ cd ../..
malachi@onyx:~/work/redo_system$ rm system.img
malachi@onyx:~/work/redo_system$ ~/work/ics/out/host/linux-x86/bin/mkyaffs2image unpacked system.img

Point our AVD to our new images
malachi@onyx:~/work/redo_system$ nano ~/.android/avd/ics.avd/config.ini
image.sysdir.2=system-images/android-14/armeabi-v7a/
image.sysdir.1=/home/malachi/work/redo_system/

The old userdata may not be compatible with your new apps. Make sure to use -wipe-data:

malachi@onyx:~/work/redo_system$ emulator -avd ics -show-kernel -scale 0.70 -memory 1024 -logcat 'dalvikvm:S,StrictMode:S,*:D' -partition-size 1024 -wipe-data

You are now running a slightly modified emulator...

15 March 2012

My New Dev Box

I'm hard on my machines. I do things like leave them number crunching at 100% cpu utilization for a week while I am out of the country.  It should be no surprise that they tend to overheat, have power supplies or hard drives go out, et cetera.

This time around, I have decided to take my time and try to head that off.  I've been collecting parts one at a time and  I'm honestly a little nervous about my first foray into watercooling -- but water + SSD should help with my core failures.



Eventually I'll add things like triple monitors at 2560x1600 each -- but for now, it's a little light on the specs:





Before we go on, I realize you may be asking a few questions.

Q: Why are you installing Windows too?
A: Unfortunately, some games I like to play are not available for Linux

Q: Why do dual-sli on a quad-sli board?
A: I was planning on buying the watercooled GTX 590; but EVGA pulled them before I could.  I'd rather try dual watercooled than quad air cooled.

Q: Why Mint instead of Ubuntu?
A: Because Unity pisses me off.  If I wanted a Mac-like interface, I would have bought one.

Q: Why Windows 7 instead of Windows 8?
A: Because that tablet UI is disgusting. If I wanted a Mac-like interface, I would have bought one.

Q: Why buy 16GB ram when the board supports 32?
A: Because the QVL didn't list any 32GB option. I decided to go with something officially supported.

Q: Why is the Linksys not in the pre-build image?
A: Half way through the build I realized that everything in my house is wireless and nothing was wired. I ran to Fry's and picked it up.

Q: Why are the pictures so crappy?
A: Unfortunately, it appears I do not have very good lighting in my house.  This was on the dining room table, with chandelier above, stand lamp next to, flash on, window curtains open and all neighboring room lights on.  It also made it near impossible to work on it after 5ish.


Ok, onto the build...

Case: Thermaltake Level 10 GT LCS
The case is really nice.  It has these ports so that wiring can easily be ran behind the motherboard instead of over.  Unfortunately for me, I rarely have cables long enough to justify it.

The case comes a few items -- mostly for the water cooling.  The open case panel in the picture comes off easily, and in fact was off for the entire build.  The back panel also comes off, though that is more of a traditional slide under-edge type (which is not easy to get back on when you are tired).



Motherboard: Asus 990FX Sabertooth
The motherboard is a very nice looking design.  I like how the heat pipes look a bit aggressive.




CPU: AMD FX-8150P
The CPU came in a nice tin - but most of the space is for the fan which we aren't going to use for this build.  According to the manual, you void your warranty if you don't use their included fan -- but I'm going the watercooling route, so...


Installing the CPU into the Motherboard

CPU installation was a breeze.  For some reason it took me awhile to find the arrow on the motherboard, but as you can see in this picture, it is clearly marked.  I'll blame the bad lighting ;)





Installing the back plate


Installing the back plate was a bit tricky.  Three of the edges were easy to get to, but the forth one required some finger contortions.  Popping it in generally caused the others to come out.  I even tried using tape to hold the others in place while I finagled the last one in.  Eventually I got it without the tape, but it probably took me a good 15 minutes to snap it into place.


CPU Waterblock

One of the reviews I read had warned to attach the hoses to the cpu waterblock before trying to put it on the CPU.  That was great advise, because it took a LOT of pressure to get it on.  To cut the correct length of tubing, I sat the waterblock in place and moved the tubing around until it looked like it would work without kinking.  If I ever upgrade the watercooling, I think I might go for something that allows a smaller turn radius.  You also need to screw the right braces onto the block.  The case comes with 3 different sets.  For AMD, they all use the same one, so it was easy enough to pick out.  The black covers were a bit difficult to get off, but a small pair of needle nose pliers worked pretty well.


Adding the quick connect was trivial -- probably the easiest part of the entire water setup.  I really hope these don't leak.  Future upgrades might include me replacing all the connections with quick connects.

Memory: Corsair CML16GX3M4A1600C9
I was really please with this memory.  While adding it to the system, those radiator fans felt very nice.  Others had warned about the CPU fan getting in the way, but that was not a problem for our water block.  I was expecting both ends to snap in place, but it appears that only one side moves.


Thermal paste

I've never had to install thermal paste before.  There was always some sticker like thing provided to use instead.  This time around I had to use the thermal paste.  I have no clue why they provided it in a syringe type of container, because all it does is bulk up at the tip like gooey wet silly putty.  I thought for a minute that maybe something was wrong with it, with the consistency.  To be honest, I don't know.  Since I am cooling with water, I'm hoping it is OK.  It that is the consistency it is supposed to be, I think the industry should really reconsider the delivery mechanism.  I tried spreading it over the entire CPU, but this was the best I was able to do.  Maybe it spread out a little bit when the water block was clamped down.  Here's hoping.



Install the water cooling block

Installing the block wasn't so bad.  It was difficult to see whether the braces were on correctly before clamping down.  Installing the quick connect was very nice.

In order to keep the tubing from hitting the fan on the side of the case, I put a twisty tie (it was holding the accessory box inside the case during shipping) to anchor it to the top of the case.  The top (and bottom actually) of the case are netted, so putting something through is very easy.

Power Supply: Thermaltake TPG-1200M
I was actually impressed with the packaging of the power supply,  In addition to a felt bag for the accessories and a felt (not plastic) bag for the power supply itself, it also came with a little velcro case for the modular cables.  I was a little confused why a couple items were outside the bags, but overall it was a nice presentation.

Installing it was easy with the exception that the provided rubber gaskets didn't fit anywhere correctly.  I ended up using them for the top holes - but IMHO the gaskets should have connected to all the screw holes.


I really like that the PSU sits in the bottom of the case; but I wish there was a little more clearance between it and the motherboard.  It made it difficult to get some of the connects in since my hand was too hide for the space.


Windows Drive: Crucial M4 2.5" 512GB SATA III SSD
Linux Drive: Crucial M4 2.5" 512GB SATA III SSD
These "drives" are small, lightweight, no moving parts and SATA III. What's not to love.  To mount them, first turn the key to Unlock, push the button while pulling the drawer out.  Screen the drive to the drawer.  Push it in.

Note in the last picture the band of power cabling.  It goes to a power connector hanging loose under the bottom drive.

Video: 2x EVGA GeForce GTX 580 FTW Hydro Copper 2 3072 MB
The video cards are pretty. And heavy.  The box contains barbs and rings for two different tubes. It also comes with the power cables and a couple adapters.



To determine which position to put the barbs, I sat the card in the case where it is going to go and again manhandled the tubing until I found a non-crinking way to tube it.  That led me to putting the one closest to the CPU further away from the water source so there was more room for the water loop.  For the second one, I decided it would be easier to do a straight tube between them than to try to curve it.

First CardSecond Card
One odd thing was that the second card had an extra rubber washer on it. At first I took the cards apart again trying to figure out which one was messed up.  When I realized that one had an extra rubber on it, it took me a minute to decide whether to remove it or not.  In the end, I decided that it might be less likely to leak with it (I hope) so I kept it on.  Assuming it doesn't cause a problem, I kinda wish I had the extra on all the other barbs.



When installing the first card, it went in without a hitch.  The second card was a bit more cramped as the sata connectors sit under it.  Well, that and I had forgotten to take the plate off:
Taking that off (10 minutes later) helped get the second card in without much additional hassle.

WiFi: Linksys WMP600N Wireless Dual-Band a/b/g/n
Earlier in the day, I remembered that everything in the house was wireless.  I also remembered that the printer worked only with the wireless computers when in wireless mode and only with the wired computers when it wired mode. Why? I don't know - never took the time to figure it out. Probably a problem with the routing on the $34 wifi router (which I also decided I am going to replace).  Long story short, I decided I wanted wifi.  I didn't want a USB adapter because my cat likes to climb on things, so I opted for a PCI card.  I was going to buy the PCIe card at Fry's instead, but then I saw the dual-band and decided that might be a better choice.





So, there aren't many standard PCI slots anymore. Mine happened to be pretty close to touching the 2nd GTX 580.  At first I was nervous about them being that close, then I thought 'hey, maybe it will get some minor cooling from the nearby waterblock'. Besides the component was on the other side.



Time to finish off the tubing

I adding the SLI connector and the small tube between the cards.

Connected the first card to the loop

And connected the second card back to the reservoir.

Connect motherboard to PSU


And power connectors to the video cards






The Diabolical USB 3.0 Header


I had actually connected some more cabling and was almost done when I tried to plug in the USB 3.0 header for the top of the case.  I probably should have seen the problem right away and shaved it -- but before I realized what was going on, one of the pins in the header was bent over.  Unfortunately, it was the one in the corner away from the graphics card, so I was having a lot of trouble getting a good angle.  I told myself I should take everything apart, but I tried one too many times and broke the pin off.  Looks like the 2 USB 3.0 jacks on the top of the case will go un-utilized.



... insert time taken to plug in all the cabling ...

Most of the cabling was easy enough to connect.  As stated, the USB 3.0 header wasn't.  The connections near the PSU were a little difficult.  The power connector for the SATA bays hiding underneath the bottom tray took a moment to find...



But the LED/Fan control didn't work.  A couple fans were spinning, water was moving, but the door fan (when closed) did not spin and none of the LEDs came on, and the LED Fan Controller had no power.  I get the impression I am missing a molex connector somewhere, but as of yet have not found it.  One thing that I thought was kind of odd is that on the back of the case, there are 3 headers but only two cables... What's that about?


There was another molex connector hiding in the cabling.  While fiddling with those wires in the 3rd picture, I noticed a loose molex inside the case.  I don't know if it fell down or what, but with that plugged in fans and leds work.  This one is black as opposed to the 2 opaque white ones - so if you don't have a black one plugged in, keep looking.  Thermaltake told me there is a 4th molex as well, but I have not found that.

BIOS too old to POST

Now, the issue appears to be that the motherboard shipped before it was technically able to run the chips they advertised it for.  Everyone online is saying that I need to swap out the CPU with an older one long enough to upgrade the BIOS.  Personally, I don't have another AM3 cpu and am kinda annoyed at the idea that I would have to buy a CPU that I would toss 5 minutes later. I emailed ASUS support - but I bet I am just delaying the inevitable.

I did find this post that mentions the BIOS being physically replaceable with this $15 part.  Looking at my board, I have confirmed that the BIOS is swappable - though it means draining the water cooling and removing the second video card (not something I look forward to trying right away).


I was going to wait until I had the board completely up and dual booting to post this -- but I have no idea how long it will take to get the BIOS replacement.  I'll go ahead and push this out now, and once I have the BIOS replacement I'll update the blog.  I definitely do not look forward to pulling out that 2nd GTX to swap the BIOS - but it's better than buying a throw-away CPU.


UPDATE:

I have the system up and running... a brief recap of what I had to do...
  1. Replace the physical BIOS with the one listed above.  This was a little harder than it should have been because I had to remove BOTH video cards to get to the BIOS since I have such a short watercooling tube between them.  Also, my chip puller appears to be too big and I didn't have the patience to go to a store the following day, so I had to use my fingers instead.
  2. After #1, the board wouldn't boot. Not even fans now.  The BIOS came with instructions stating that you may need to reset the CMOS. Ok.
  3. Tried the jumper first. No go.
  4. Pulled both video cards again so that I could get to the battery. Reset the battery and the jumper at the same time.  Let me tell you, if you are installing the new BIOS -- get it before putting the board together because removing both of these cards is not fun.
  5. Woot! Boots into BIOS. Looked over the settings.  I thought it seemed to be running a little hot (already at 89*F) but, I have never used water cooling before, so not sure what to expect.
  6. Install Windows [yes, I know - but I need it for some of my games]. Install some driver CDs for the motherboard, network card, etc. Installed the EVGA drivers and upon reboot... wait, no, it wouldn't reboot. damn. CPU LED, DRAM LEEEEEDDDDDDDDDDDDDDDD... hmmm
  7. MemOK button.
  8. CPU LED, DRAM LED, VIDEO LED, repeat, repeat repeat repeat... damn
  9. I contacted Asus who not only called me but gave me their email addresses so that I could get back in touch with them.  Very professional. 
  10. Tried jumper again. No good.
  11. Tried just 1 stick of memory. No good.
  12. Tried removing SLI bridge (it was the evga drivers installed last, right?). No good.
  13. Ok, let's try removing the video cards... as I went to pull them out, I noticed the network card is not sitting all the way down.  I try to reseat it and notice it *won't* sit all the way down? WTF?  Well, it came with another plate... checking - no that one is shorter... damn... pliers... bend faceplate... Ah, not it seats properly.  Checking - hey! It boots.
  14. Put everything together and test booting throughout. Everything is fine now.
  15. Ok, with Windows installed, now let's install LMDE (Linux Mint Debian Edition).
  16. Booting, live boot failure?... and black screen.  Damn. 
  17. Some internet search and chats later (thanks Jon for finding it) I add 'nomodeset' to the grub boot command. It boots.
  18. Install LMDE (with grub on the Windows drive). Reboot. Black screen. Damn.
  19. Saw this post and decided to make it permanent..
sudo su - root
nano /etc/default/grub
And to make the change work even for recovery mode...
GRUB_CMDLINE_LINUX="nomodeset"
And then to update it and reboot:
update-grub
reboot
And we're good to go. I have Windows 7 and LMDE dual booting now. Now it's time to re-install my entire development environment ;)








18 February 2012

AIDL Step 7: Using Callbacks

What if we would like the service to callback into the client?

Rather than repeating all the previous tutorials, let's just alter the v2 API. Note: In the real world, you should never do that as you need to remain backward compatible. Since I know no one else is using the api, we'll cheat.

First, let's create API/src/main/java/org/eoti/android/test/apkextensions/api/v2/IServerCallback.aidl:
package org.eoti.android.test.apkextensions.api.v2;

oneway interface IServerCallback {
    void message(String text);
}


And update our IServer.aidl:
package org.eoti.android.test.apkextensions.api.v2;

import org.eoti.android.test.apkextensions.api.v2.Registration;
import org.eoti.android.test.apkextensions.api.v2.IServerCallback;

interface IServer
{
    void register(inout Registration registration, IServerCallback cb);
    void unregister(in String registrationName, IServerCallback cb);
    String getServerIdentifier();
}


Recompile our API
malachi@onyx:~/work/apkextensions/API$ mvn clean install


Next, we update our server.  Replace createV2Binder() with:
    final RemoteCallbackList<IServerCallback> callbacks = new RemoteCallbackList<IServerCallback>();

    protected void broadcast(String message)
    {
        final int N = callbacks.beginBroadcast();
        for(int i=0; i<N; i++)
        {
            try {
                callbacks.getBroadcastItem(i).message(message);
            } catch (RemoteException e) {
                // RemoteCallbackList will take care of removing dead objects
            }
        }
        callbacks.finishBroadcast();
    }
   
    private IBinder createV2Binder()
    {
        return new IServer.Stub(){
            @Override
            public void register(Registration registration, IServerCallback cb) throws RemoteException {
                if(registration == null) throw new RemoteException(); // API15 required for 'new RemoteException(string)'
                registrations.put(registration.getName(), registration);
                if(cb != null)
                {
                    callbacks.register(cb);
                    cb.message("Registration successful");
                }
                Log.d(TAG, "Registration received:  " + registration.getName() + " v" + registration.getVersion());
                broadcast(registration.getName() + " has entered the room");
            }

            @Override
            public void unregister(String registrationName, IServerCallback cb) throws RemoteException {
                if(registrationName == null)  throw new RemoteException(); // API15 required for 'new RemoteException(string)'
                registrations.remove(registrationName);
                if(cb != null)
                {
                    cb.message("Unregistration successful");
                    callbacks.unregister(cb);
                }
                Log.d(TAG, "Registration removed: " + registrationName);
                broadcast(registrationName + " has left the room");
            }

            @Override
            public String getServerIdentifier() throws RemoteException {
                return TheServer.class.getName() + "#" + TheServer.this.hashCode();
            }
        };
    }

And compile it
malachi@onyx:~/work/apkextensions/TheServer$ mvn clean install


Next, we'll update our v2 client...
In our Client2/src/main/java/org/eoti/android/test/apkextensions/client2/Client2Activity.java, add:
    private IServerCallback callback = new IServerCallback.Stub() {
        @Override
        public void message(String text) throws RemoteException {
            Log.d(TAG, "Message received: " + text);
        }
    };

change:
server.register(registration);
to:
server.register(registration, callback);

and change:
server.unregister(REG_NAME);
to:
server.unregister(REG_NAME, callback);

Rebuild it
malachi@onyx:~/work/apkextensions/Client2$ mvn clean install

Run Client1:
I/ActivityManager(   61): Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=org.eoti.android.test.apkextensions.client1/.Client1Activity } from pid 127
I/ActivityManager(   61): Start proc org.eoti.android.test.apkextensions.server for service org.eoti.android.test.apkextensions.server/.TheServer: pid=846 uid=10045 gids={1015}
E/Client1 (  546): Server bound
I/ActivityManager(   61): Displayed org.eoti.android.test.apkextensions.client1/.Client1Activity: +726ms
D/Client1 (  546): Server connected
D/TheServer(  846): Registration received from deprecated client:  org.eoti.android.test.apkextensions.client1.Client1Activity
D/Client1 (  546): Registered org.eoti.android.test.apkextensions.client1.Client1Activity
D/Client1 (  546): Running tests...
D/Client1 (  546): Tests done...
D/TheServer(  846): Registration removed from deprecated client: org.eoti.android.test.apkextensions.client1.Client1Activity
E/Client1 (  546): Server unbound

Run Client2:
W/KeyCharacterMap(  546): No keyboard for id 0
W/KeyCharacterMap(  546): Using default keymap: /system/usr/keychars/qwerty.kcm.bin
I/ActivityManager(   61): Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=org.eoti.android.test.apkextensions.client2/.Client2Activity } from pid 127
I/ActivityManager(   61): Start proc org.eoti.android.test.apkextensions.client2 for activity org.eoti.android.test.apkextensions.client2/.Client2Activity: pid=856 uid=10047 gids={1015}
E/Client2 (  856): Server bound
D/Client2 (  856): Server connected: org.eoti.android.test.apkextensions.server.TheServer#1079096160
D/Client2 (  856): Message received: Registration successful
D/TheServer(  846): Registration received:  org.eoti.android.test.apkextensions.client2.Client2Activity v1.0alpha
D/Client2 (  856): Message received: org.eoti.android.test.apkextensions.client2.Client2Activity has entered the room
D/Client2 (  856): Registered org.eoti.android.test.apkextensions.client2.Client2Activity
D/Client2 (  856): Running tests...
D/Client2 (  856): Tests done...
D/TheServer(  846): Registration removed: org.eoti.android.test.apkextensions.client2.Client2Activity
D/Client2 (  856): Message received: Unregistration successful
E/Client2 (  856): Server unbound
I/ActivityManager(   61): Displayed org.eoti.android.test.apkextensions.client2/.Client2Activity: +931ms


This all looks good -- but why didn't Client1 announce when Client2 entered the room? 
It's because Client1 is still using the old deprecated API.

AIDL Step 6: Creating an updated client

Create our new module:

malachi@onyx:~/work/apkextensions$ mvn archetype:generate -DarchetypeCatalog=http://repository-malachid.forge.cloudbees.com/public-snapshot/archetype-catalog.xml
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building extension test 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] >>> maven-archetype-plugin:2.2:generate (default-cli) @ pom >>>
[INFO]
[INFO] <<< maven-archetype-plugin:2.2:generate (default-cli) @ pom <<<
[INFO]
[INFO] --- maven-archetype-plugin:2.2:generate (default-cli) @ pom ---
[INFO] Generating project in Interactive mode
[INFO] No archetype defined. Using maven-archetype-quickstart (org.apache.maven.archetypes:maven-archetype-quickstart:1.0)
Choose archetype:
1: http://repository-malachid.forge.cloudbees.com/public-snapshot/archetype-catalog.xml -> org.eoti.kryten:kryten-archetype (kryten-archetype)
2: http://repository-malachid.forge.cloudbees.com/public-snapshot/archetype-catalog.xml -> org.eoti.galatea:galatea-archetype (galatea-archetype)
3: http://repository-malachid.forge.cloudbees.com/public-snapshot/archetype-catalog.xml -> org.eoti.archtest:archtest-archetype (archtest-archetype)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): : 2
Define value for property 'groupId': : org.eoti.android.test.apkextensions.client2
Define value for property 'artifactId': : Client2
Define value for property 'version':  1.0-SNAPSHOT: :
Define value for property 'package':  org.eoti.android.test.apkextensions.client2: :
Confirm properties configuration:
groupId: org.eoti.android.test.apkextensions.client2
artifactId: Client2
version: 1.0-SNAPSHOT
package: org.eoti.android.test.apkextensions.client2
 Y: :
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: galatea-archetype:1.1-SNAPSHOT
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: org.eoti.android.test.apkextensions.client2
[INFO] Parameter: artifactId, Value: Client2
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: org.eoti.android.test.apkextensions.client2
[INFO] Parameter: packageInPathFormat, Value: org/eoti/android/test/apkextensions/client2
[INFO] Parameter: package, Value: org.eoti.android.test.apkextensions.client2
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: groupId, Value: org.eoti.android.test.apkextensions.client2
[INFO] Parameter: artifactId, Value: Client2
[WARNING] Don't override file /home/malachi/work/apkextensions/Client2/src/main/android/res/values/strings.xml
[WARNING] Don't override file /home/malachi/work/apkextensions/Client2/src/main/android/res/layout/main.xml
[INFO] project created from Archetype in dir: /home/malachi/work/apkextensions/Client2
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 19.317s
[INFO] Finished at: Sat Feb 18 08:09:07 PST 2012
[INFO] Final Memory: 11M/245M
[INFO] ------------------------------------------------------------------------


Take care of the keystore and dependency...

And replace our Client2Activity:
package org.eoti.android.test.apkextensions.client2;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import org.eoti.android.test.apkextensions.api.v2.IServer;
import org.eoti.android.test.apkextensions.api.v2.Registration;

public class Client2Activity extends Activity {
    private static String TAG = "Client2";

    private static final String REG_NAME = Client2Activity.class.getName();
    private static final String REG_VERSION = "1.0alpha";
    private enum State{Unbound,Bound,Connected,Disconnected}
    private State state = State.Unbound;
    private IServer server;

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            server = IServer.Stub.asInterface(iBinder);
            state = State.Connected;
            try {
                Log.d(TAG, "Server connected: " + server.getServerIdentifier());
                Registration registration = new Registration();
                registration.setName(REG_NAME);
                registration.setVersion(REG_VERSION);
                server.register(registration);
                Log.d(TAG, "Registered " + registration.getName());
            } catch (RemoteException e) {
                Log.e(TAG, "Unable to register", e);
            }

            doTest();
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            state = State.Disconnected;
            server = null;
            Log.d(TAG, "Server disconnected");
        }
    };

    private void bindServer()
    {
        switch(state)
        {
            case Bound:
            case Connected:
            case Disconnected:
                return;
            default:
                bindService(new Intent(IServer.class.getName()), connection, Context.BIND_AUTO_CREATE);
                state = State.Bound;
                Log.e(TAG, "Server bound");
                break;
        }
    }

    private void unbindServer()
    {
        switch(state)
        {
            case Unbound:
                return;
            case Connected:
                try{
                    server.unregister(REG_NAME);
                }catch(RemoteException e){
                    Log.e(TAG, "Unable to unregister", e);
                }
                server = null;
                // fall through
            default:
                unbindService(connection);
                state = State.Unbound;
                Log.e(TAG, "Server unbound");
                break;
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        bindServer();
    }

    @Override
    protected void onDestroy() {
        unbindServer();
        super.onDestroy();
    }

    protected void doTest()
    {
        Log.d(TAG, "Running tests...");
        // @TODO add tests here
        Log.d(TAG, "Tests done...");
        unbindServer();
    }
}

Build it... run it...
malachi@onyx:~/work/apkextensions$ cd Client2
malachi@onyx:~/work/apkextensions/Client2$ mvn clean install

I/ActivityManager(   61): Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=org.eoti.android.test.apkextensions.client2/.Client2Activity } from pid 127
I/ActivityManager(   61): Start proc org.eoti.android.test.apkextensions.client2 for activity org.eoti.android.test.apkextensions.client2/.Client2Activity: pid=697 uid=10047 gids={1015}
I/ActivityManager(   61): Start proc org.eoti.android.test.apkextensions.server for service org.eoti.android.test.apkextensions.server/.TheServer: pid=705 uid=10045 gids={1015}
E/Client2 (  697): Server bound
I/ActivityManager(   61): Displayed org.eoti.android.test.apkextensions.client2/.Client2Activity: +1s67ms
D/Client2 (  697): Server connected: org.eoti.android.test.apkextensions.server.TheServer#1079078320
D/TheServer(  705): Registration received:  org.eoti.android.test.apkextensions.client2.Client2Activity v1.0alpha
D/Client2 (  697): Registered org.eoti.android.test.apkextensions.client2.Client2Activity
D/Client2 (  697): Running tests...
D/Client2 (  697): Tests done...
D/TheServer(  705): Registration removed: org.eoti.android.test.apkextensions.client2.Client2Activity
E/Client2 (  697): Server unbound
I/ActivityManager(   61): No longer want com.android.settings (pid 155): hidden #16


So we get deprecated messages when Client1 connects and the additional functionality when Client2 connects.
Client2 also has access to functionality that Client1 is unaware of.

Next up, AIDL Step 7: Using Callbacks

AIDL Step 5: Upgrading the Service

Let's update our service to handle an additional intent...

First, in the AndroidManifest.xml it should now read:
        <service android:name=".TheServer">
            <intent-filter>
                <action android:name="org.eoti.android.test.apkextensions.api.v1.IServer"/>
                <action android:name="org.eoti.android.test.apkextensions.api.v2.IServer"/>
            </intent-filter>
        </service>

Then, let's update TheServer.java to handle both old and new clients:
package org.eoti.android.test.apkextensions.server;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import org.eoti.android.test.apkextensions.api.v2.IServer;
import org.eoti.android.test.apkextensions.api.v2.Registration;

import java.util.concurrent.ConcurrentHashMap;

public class TheServer extends Service
{
    private static final String TAG = TheServer.class.getSimpleName();
    private ConcurrentHashMap<String, IBinder> binders = new ConcurrentHashMap<String, IBinder>();
    private ConcurrentHashMap<String, Registration> registrations = new ConcurrentHashMap<String, Registration>();

    private IBinder createV2Binder()
    {
        return new IServer.Stub(){
            @Override
            public void register(Registration registration) throws RemoteException {
                if(registration == null) throw new RemoteException(); // API15 required for 'new RemoteException(string)'
                registrations.put(registration.getName(), registration);
                Log.d(TAG, "Registration received:  " + registration.getName() + " v" + registration.getVersion());
            }

            @Override
            public void unregister(String registrationName) throws RemoteException {
                if(registrationName == null)  throw new RemoteException(); // API15 required for 'new RemoteException(string)'
                registrations.remove(registrationName);
                Log.d(TAG, "Registration removed: " + registrationName);            }

            @Override
            public String getServerIdentifier() throws RemoteException {
                return TheServer.class.getName() + "#" + TheServer.this.hashCode();
            }
        };
    }

    private IBinder createV1Binder()
    {
        return new org.eoti.android.test.apkextensions.api.v1.IServer.Stub(){
            @Override
            public void register(org.eoti.android.test.apkextensions.api.v1.Registration registration) throws RemoteException {
                if(registration == null) throw new RemoteException(); // API15 required for 'new RemoteException(string)'
                registrations.put(registration.getName(), new Registration(registration));
                Log.d(TAG, "Registration received from deprecated client:  " + registration.getName());
            }

            @Override
            public void unregister(String registrationName) throws RemoteException {
                if(registrationName == null)  throw new RemoteException(); // API15 required for 'new RemoteException(string)'
                registrations.remove(registrationName);
                Log.d(TAG, "Registration removed from deprecated client: " + registrationName);
            }
        };
    }
  
    public IBinder onBind(Intent intent) {
        if(intent == null) return null;
        String action = intent.getAction();
        if(action == null) return null;
        IBinder binder = binders.get(action);
        if(binder != null)
            return binder;
      
        if(org.eoti.android.test.apkextensions.api.v1.IServer.class.getName().equals(action))
            binder = createV1Binder();

        if(IServer.class.getName().equals(action))
            binder = createV2Binder();

        if(binder != null)
            binders.put(action, binder);
      
        return binder;
    }

    @Override
    public void onDestroy() {
        registrations.clear();
        super.onDestroy();
    }
}


Make sure it all compiles...
malachi@onyx:~/work/apkextensions$ cd TheServer/
malachi@onyx:~/work/apkextensions/TheServer$ mvn clean install


And make sure the now deprecated client can still connect:
I/ActivityManager(   61): Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=org.eoti.android.test.apkextensions.client1/.Client1Activity } from pid 127
I/ActivityManager(   61): Start proc org.eoti.android.test.apkextensions.server for service org.eoti.android.test.apkextensions.server/.TheServer: pid=605 uid=10045 gids={1015}
E/Client1 (  546): Server bound
I/ActivityManager(   61): Displayed org.eoti.android.test.apkextensions.client1/.Client1Activity: +671ms
D/Client1 (  546): Server connected
D/TheServer(  605): Registration received from deprecated client:  org.eoti.android.test.apkextensions.client1.Client1Activity
D/Client1 (  546): Registered org.eoti.android.test.apkextensions.client1.Client1Activity
D/Client1 (  546): Running tests...
D/Client1 (  546): Tests done...
D/TheServer(  605): Registration removed from deprecated client: org.eoti.android.test.apkextensions.client1.Client1Activity
E/Client1 (  546): Server unbound


Next up, AIDL Step 6: Creating an updated client