Visar inlägg med etikett HowTo. Visa alla inlägg
Visar inlägg med etikett HowTo. Visa alla inlägg

2013-11-11

parse.com-php-library - Contributed with a function in Github

We are using parse.com as the server backend in a project. The administration interface is written in PHP so we are using parse.com-php-library to easily integrate with parse.com.
parse.com-php-library is open source and available on GitHub.

Today we needed a function to filter our records on a specific date in the database.
The most obvious way to solve it was to combine a call to whereGreaterThanOrEqualTo() with a call to whereLessThanOrEqualTo(), but it will not work since it will handle it as an OR query.

The solution was to add a new function to parseQuery.php:
public function whereBetweenOrEqualTo($key, $startValue, $endValue){
   if(isset($key) && isset($startValue) && isset($endValue)){
      $this->_query[$key] = array(
         '$gte' => $startValue,
         '$lte' => $endValue
      );
   }        
   else{
      $this->throwError('the $key, $startValue and $endValue parameters must be set when setting a "where" query method');                
   }
}

We could just have added it to our own parseQuery.php and continued to work, but since it is open source and on GitHub it is very easy to contribute to the original project.

  1. Fork the project you would like to contribute to - Go to the project at the GitHub web page and click the Fork button.
  2. Clone the code to your computer. There is an application for GitHub that will handle repositories on GitHub for you which will make cloning, committing and other tasks very easy.
  3. Make changes to the code.
  4. Commit your changes to your local repository. Use the GitHub application to make it easier.
  5. Push your changes to the remote server. It is called Sync in the GitHub application.
Your changes are now in your the repository that you forked. Make a pull request to let the original repository now that you have made some great additions and changes to the code.

  1. Go to your project and click Pull requests and then New pull request.
  2. Follow the instructions to create a pull request for your changes and write an informative comment: "Fixes #117. Query for between or equal to start and end value."
    Hash + issue number will automatically be linked to the issue number.
  3. If the owner of the original repository likes the changes he/she will add them to the repository.

2012-11-13

Obfuskera Androidapp

Den Androidapp jag ska obfuskera är MantisDroid och den består av ett library (MantisDroidLibrary) och 2 appar: en gratisversion (MantisDroid Free) och en betalversion (MantisDroid).
Jag använder ProGuard för obfuskering och optimering. Det verktyget följer med Android SDK:n.

Först måste konfigurationsfilerna för ProGuard rättas till. I foldern <android-sdk>/tools/proguard/ ska filerna proguard-android.txt och proguard-android-optimize.txt editeras:
#-keep public class com.google.vending.licensing.ILicensingService
#-keep public class com.android.vending.licensing.ILicensingService
Dvs. kommentera bort de 2 raderna eftersom de annars ger fel i obfuskeringen.

Jag använder Ant för att bygga mina projekt så om du inte redan gjort det så kör följande kommando i din projektkatalog:
android update project -p .
Den skapar upp bland annat build.xml och lite andra filer som behövs.

Börja med projektet MantisDroidLibrary.
Den ska inte obfuskeras i sitt projekt utan gör det när apparna byggs och obfuskeras.
Se till att obfuskering inte är aktiverad genom att titta i project.properties och verifiera att direktivet proguard.config inte är aktiverad.

De två apparna har precis samma inställningar så gör samma sak i båda.
Öppna project.properties och kommentera fram följande för endast obfuskering:
proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
Om du vill ha både obfuskering och optimering så lägg till följande:
proguard.config=${sdk.dir}/tools/proguard/proguard-android-optimize.txt:proguard-project.txt

I filen proguard-project.txt i projekt så ska nu projektspecifika regler läggas till. Skapa filen om den inte redan finns.
I min skriver jag:
-dontwarn org.xmlpull.v1.**
-assumenosideeffects class android.util.Log {
    public static *** d(...);
    public static *** v(...);
}
-keep public class com.google.android.vending.licensing.ILicensingService

Den första regeln döljer alla varningar för klasser i paketet org.xmlpull.v1. Det gör jag eftersom det blir krockar mellan klasser i android.jar och ett library (ksoap2-android) som jag använder. I mitt fall är det ingen fara utan helt ok.
Den andra regeln är en optimeringsregel som ser till att optimera bort alla anrop till Log.d(...) och Log.v(...). Anledningen till det är att de loggningarna inte ska komma ut i mina apk:er eftersom de avslöjar vad olika delar av min kod gör. Samtidigt så ska inte debug- och verbose-loggning finnas ute i kundernas installationer.
Den tredje regeln är för att jag använder Google Play Licensing och inte vill att den ska optimeras bort.

För att signeringen ska fungera så behöver man fylla i key.store och key.alias i filen ant.properties:
key.store=mantisdroid_release_key.keystore
key.alias=mantisdroid

key.alias.password=[your_password]
key.store.password=[your_password]
Library-projektet behöver inte signeras så skriv inte någon key.store eller key.alias där.

Kompilera, obfuskera, optimera och signera sedan appen med Ant genom att köra (i projektets folder):
ant release

2012-11-02

Dekompilering av Android-appar

Först och främst skriver jag det här för att belysa den problematik som finns när det gäller apputveckling och hur lätt det är att kopiera appar och kringgå olika typer av skydd. Det är inte tänkt att vara en tutorial för hur man piratkopierar appar.
För att förhindra otillåten kopiering och användande av en app så går det att aktivera Google Play Licensing. Hur man implementerar det är upp till varje utvecklare, men lämpligt är att appen då och då kontrollerar mot Google Play om appen är installerad på korrekt sätt, dvs. genom Google Play och att eventuella avgifter är betalda. Om Google Play svarar att appen inte är licensierad så ber man användaren att köpa appen.

Problemet är att det ganska lätt går att dekompilera appen och ta bort licensieringskontrollen och sedan använda appen helt fritt.

Hur gör man det?
Skaffa först verktygen. Installera/packa upp på lämplig plats.
dex2jar: http://code.google.com/p/dex2jar/ - Läser dex-filer och skapar jar-filer.
JD-GUI: http://java.decompiler.free.fr/?q=jdgui - Grafiskt verktyg för att visa innehållet i .class-filer.

På Ubuntu kör jag följande kommando för att få ut en jar av en apk:
d2j-dex2jar.sh android-app.apk
Resultatet ut från den är filen android-app.jar

Starta JD-GUI och öppna android-app.jar.
Voila! All kod i klartext!

Härifrån är det lätt att ändra på det man behöver för att till exempel ta bort licensiering eller aktivera olika funktioner. Packa slutligen ihop appen och signera den. Sen är den klar att användas igen.

Hur gör man för att skydda sig från det här? Svaret heter obfuskering. Det gör koden väldigt mycket svårare att läsa eftersom de flesta namn översätts till enstaka bokstäver.
Här kan ni läsa om hur man obfuskerar med hjälp av ProGuard:
http://developer.android.com/tools/help/proguard.html

Obfuskering ger ett bra skydd, men ha ändå för vana att gå igenom den obfuskerade koden med JD-GUI och kontrollera känsliga punkter i koden och se till att det inte är för uppenbart vad som händer och hur man kan kringgå det. Alla klasser och metoder byter inte namn så det kan gå att luska ut hur det fungerar.
Det är viktigt att se till att loggning inte avslöjar vad som händer.
Ett enkelt och snabbt sätt är att låta ProGuard ta bort alla anrop till Log.d och Log.v med följande direktiv (observera att optimization måste vara påslagen):
-assumenosideeffects class android.util.Log {
    public static *** d(...);
    public static *** v(...);
}



Slutsats
Om du vill skydda din app så lägg in Google Play Licensing och obfuskera sedan all kod. Det viktigaste skyddet är ändå att du är medveten om problematiken och hur enkelt det är att titta i din kod, så kolla själv innan någon annan gör det.

2012-09-29

Gratis- och betalapp med samma kodbas

Jag har efter lanseringen av MantisDroid insett att jag även behöver publicera en gratisversion, så att man kan ladda hem appen och se om den är något att ha innan man betalar för fullversionen. Visst, man har 15 minuter på sig att testa en betalapp, men det är inte många som gör det eftersom 15 minuter är så väldigt kort tid.

Lösningen är alltså att skapa en gratisversion av min app med begränsad funktionalitet.

Hur gör man en gratisversion utan att duplicera all kod?

Det är faktiskt ganska enkelt. En app kan använda en annan app och referera till den som ett library (bibliotek). På så sätt kan den nya appen utnyttja allt i den andra. Dock kan inte en app som markerats som library längre vara en app som går att installera på en enhet. Därför behövs det 3 projekt: En betalapp, en gratisapp och en library-app. I mitt fall så ligger all kod i betalappen som redan är publicerad på Play Store.
  1. Skapa library-appen genom att skapa ett nytt projekt med lämpligt paketnamn, ex. se.nextsource.android.mantisdroid
    Det är samma paketnamn som i betalappen, men det gör ingenting.
  2. Öppna library-appen i Eclipse, ta fram properties för projektet, klicka på Android och markera checkboxen "Is Library".
  3. Flytta kod och resurser som ska vara gemensamma från betalappen till library-appen.
  4. Öppna properties för betalappen, klicka på Android och lägg till library-appen som ett Library.
  5. Skapa gratisappen som ett nytt projekt med nytt paketnamn, ex. se.nextsource.android.mantisdroidfree 
  6. Öppna properties för gratisappen, klicka på Android och lägg till library-appen som ett Library.
  7. Deklarera de komponententer (activity, service, receiver, provider etc. och även permissions, uses-library etc.) som gratis- och betalappen använder från library-appen.
  8. Nu har du 2 appar med exakt samma kodbas men med olika paketnamn.
  9. För att särskilja versionerna mellan varandra så jag lagt till en ny Application-klass i gratis- och betalapp-projekten som ärver från en Application-klassen som ligger i library-appen. Vid onCreate så sätts vilken version som körs i superklassen och sedan anropas super.onCreate(). I superklassens onCreate så slår jag av och på olika funktioner som ska skilja applikationerna åt.
Ref: http://developer.android.com/tools/projects/projects-eclipse.html

Problem jag stötte på
-Se till att ingen duplicering av kod eller res-filer finns för då bygger det inte!
-Från SDK 14 så går det inte att använda resId i switch-case i ett library eftersom de inte längre är final så ändra det med refactor-funktionen i Eclipse till if-else. Irriterande, men det finns en bra förklaring:
http://android-developers.blogspot.se/2011/10/changes-to-library-projects-in-android.html
-Proguard fick svårt att obfuskera koden i biblioteket så jag var tvungen att lägga till följande i proguard.cfg:
-keep public class com.google.android.vending.licensing.ILicensingService
-dontwarn org.xmlpull.v1.**
-dontwarn android.support.**
Obs! Tänk på att lägga till det i alla 3 proguard.cfg.

Resultatet
Nu när allt är klart så behöver jag bara göra följande för att införa kodändringar, bygga och publicera mina 2 appar:
  1. Gör ändringar och fixar i library-appen.
  2. Ändra versionsnumret i apparna så att de stegas upp.
  3. Kör kommandot: ant release på båda apparna så att de bygger, obfuskeras och signeras för release.
  4. Testkör båda apparna så att de fungerar och ändringarna är testade.
  5. Lägg upp de 2 nya apk:erna på Play Store.
  6. Klart!

2012-05-21

Problem med View Swiping och orientation changes


Jag har byggt en app med View Swiping enligt den här artikeln. Jag har gjort några ändringar för att det ska passa min app.

Min implementation:
En FragmentActivity som har en ViewPager.
ViewPager har en FragmentPagerAdapter.
I FragmentPagerAdapter så skapas det upp 6 stycken ListFragment.
Varje ListFragment har en ArrayAdapter som ska hålla sina Items.
Jag lägger till Items till varje adapter beroende på vilken status ett item har med:
fragmentPagerAdapter.getItem(listIndex).getAdapter ().add(item);
Det fungerar galant första gången jag visar mina listfragment.

Problemet:
När jag vrider på telefonen så skapas allt om via onDestroy() och onCreate() som det ska, men mina listfragment blir tomma. Jag har skapat om dem så det borde bli samma sak som första gången jag laddar allt.
Felet beror på att Android återanvänder listfragmenten som skapades första gången och de är inte samma som jag lägger till mina items till. Därför blir listorna tomma.
Jag använder getItem() för att få tag i mitt listfragment, men det gör inte Android. Den anropar inte ens getItem() andra gången utan letar reda på dem via instantiateItem().

Min lösning:
Lösningen känns lite som ett hack, men fungerar väldigt bra (det skapas dock upp några extra fragment, men det är nog inte jätteofta som användarna kommer att vrida telefonen fram och tillbaks just i listvyn där det här används):
Gör en override på: public Object instantiateItem(ViewGroup container, int position) i FragmentPagerAdapter:
Kod:
@Override
public Object instantiateItem(ViewGroup container, int position) {
ItemsFragment fragment = (ItemsFragment) super.instantiateItem(container, position);
if(fragment.getAdapter() == null) {
ItemsFragment myFragment = getItem(position);
fragment.setListAdapter(myFragment.getAdapter());
fragment.setTitle(myFragment.getTitle());
}
return fragment;
}
Notera: Mitt ItemsFragment har en ny metod getAdapter() som returnerar en typad adapter "ItemListAdapter" och nya metoder för set och getTitle() som används för att få rätt texter på PagerTitleStrip i ViewPagern.

Jag använder min metod getAdapter() för att kolla om det funna fragmentet har en adapter. Om den inte har det så tar jag en adapter via metoden getItem() som returnerar fragments som jag har kontroll på.

Ni kanske undrar varför jag helt enkelt inte bara lägger till:
android:configChanges= "orientation|keyboardHidden|screenSize"
i manifestet. För det första så ska den tekniken undvikas eftersom det gör hanteringen av resources onödigt komplicerad. Vidare så hanteras inte ActionBar och PagerTitleStrip korrekt.

Nästa steg nu är att använda:
onRetainCustomNonConfigurationInstance/getLastCustomNonConfigurationInstance
för att slippa ladda om all data mellan vridningarna av telefonen.

Ref: Mitt inlägg på Swedroid

2012-05-02

NFC-taggar


Igår kom jag på att jag ville använda NFC för att slå av och på framför allt Wi-Fi och blåtand. Sedan dess har jag hållt på och kollat på olika taggar och olika appar för att hantera det. Jag fastnade för Xperia SmartTags, men fick inte appen med Widgeten att funka i min Nexus S. Troligtvis köper jag nog SmartTags ändå eftersom man får 4 snygga taggar för 120 kr + frakt (hos Dustin). Droidstuffs taggar var inte så snygga...tyvärr. Skaffa snyggare taggar så köper jag dem därifrån direkt! :-)
Idag fick jag tips om pluginen Locale NFC Plugin som fungerar ihop med Tasker som jag köpt för länge sedan. Nu kan jag använda RFID-taggen i mitt Västtrafikkort till att slå av Wi-Fi och blåtand, sätta ned ljusstyrkan och höja ringsignalen till max.

Fördelen med Locale NFC Plugin är att den funkar med alla taggar, inkl. de icke skrivbara.

Om någon undrar så gjorde jag så här:
1. Öppna Tasker och gå till fliken Tasks
2. Skapa nytt Task och döp till lämpligt namn "NFC Out"
3. Lägg till de Actions du önskar göra när Tasken körs: Wi-Fi off, BT off, Ringer Volume 7 etc.
4. Spara ditt Task
5. Gå till fliken Profiles
6. Skapa ny profil och döp till lämpligt namn "NFC Out"
7. Välj Context: State->Plugin->Locale NFC Plugin
8. Tryck Edit och håll din telefon mot taggen, tryck Ok
9. Spara och välj den Task du tidigare skapade (NFC Out)
10. Testa! Fungerar grymt bra!

2012-03-15

Logga in på dator utan lösenord, dvs. public key authentication

För att slippa skriva in lösenord varje gång man loggar in på en dator via SSH så kan man använda en publik nyckel för att autentisera användaren.

Skapa först upp publik och privat nyckel:
$ ssh-keygen -t dsa
Följ instruktionerna och välj ett lösenord som skiljer sig från användarens lösenord
Den publika nyckeln hamnar här: /home/[user]/.ssh/id_dsa.pub
Den private nyckeln hamnar här: /home/[user]/.ssh/id_dsa

Flytta den publika nyckeln till den dator du vill logga in på:
$ scp ~/.ssh/id_dsa.pub [user]@[server]:.ssh/authorized_keys

Nu kan du logga in på den andra datorn utan att kunna lösenordet för kontot där
$ ssh [user]@[server]
$ scp [file] [user]@[server]:/[dir]
Dock måste du skriva in lösenordet till nyckelfilen varje gång.

För att slippa skriva in det lösenordet varje gång så skriver man:
$ exec /usr/bin/ssh-agent $SHELL
$ ssh-add

Klart! Nu kan du logga in utan att skriva några lösenord!

För att ändra lösenordet till nyckelfilen så skriver man:
$ ssh-keygen -f ~/.ssh/id_dsa -p

2012-02-02

Blockera besökare på Blogger/Blogspot

Googles bloggverktyg Blogger/Blogspot har inte någon blockeringsfunktion för enskilda användare. Det är allt eller inget genom inställningen under Inställningar - Basfunktioner - Tillstånd - Bloggläsare. Rättighetern som finns tillgängliga är Alla, Endast bloggskribenter och Enbart de här läsarna. Det fungerar bra för bloggar man vill hålla privata till ett begränsat antal läsare, men fungerar inte alls bra när man vill blockera några få.

Går det att blockera enskilda användare även fast det saknas stöd i Blogger?
Ja, med hjälp av lite javascript och klipp och klistra så löser man det genom att blockera vissa IP-adresser eller host-namn.
Det går hyffsat lätt att ta sig förbi skyddet genom att använda en proxy, men jag tror ändå att ett skydd byggt med javascript hindrar majoriteten av de man vill slippa. En proxy kräver ändå ett antal extrasteg och ofta att man betalar för tjänsten.

Hur gör man?
Jag har hämtat inspiration från följande sidor:
http://www.javascriptkit.com/script/script2/blockip.shtml
http://stackoverflow.com/questions/102605/can-i-lookup-the-ip-address-of-a-hostname-from-javascript

Gå till Blogger (kontrollpanelen för bloggarna på Blogspot) och klicka på din blogg.
Gå till menyvalet Layout och klicka på "Lägg till Gadget".
Rulla ner till "HTML/JavaScript" och tryck på +-knappen för att lägga till den.
Skriv ingen titel utan klistra endast in följande i rutan för Innehåll:
http://pastie.org/3301106 (Klicka på länken och hämta koden från Pastie)

Ändra följande rad och fyll i de IP-adresser du vill blockera (exempel på 2 olika adresser):
var bannedIps=["23.23.23.23", "11.11.11"]
Skriptet matchar början på en IP-adress så för att blockera alla IP-adresser som börjar med 23.23.23 skriver du följande:
var bannedIps=["23.23.23"]
Det går även att matcha hostname om du kör din egen tjänst för att slå upp IP-adresser och hostname. Blockera hostname genom att ändra följande rad:
var bannedHosts=["host1.se", "host2.se"];
Tryck slutligen på Spara
Det läggs nu till en gadget som heter "HTML/Javascript" och kommer skicka alla besökare med de inmatade IP-adresserna till http://www.google.com
Om du vill ändra adress till var de skickas så ändrar du adressen i följande rad:
window.location.replace("http://www.google.com");

Vad gör skriptet?
Den börjar med att se till att det garanterat finns en funktion för att hämta ut alla element via ett klassnamn. Vissa webbläsare har inte metoden getElementsByClassName.
Sedan kommer själva blockeringsfunktionen som matchar en lista med blockerade IP-adresser mot den IP-adress som skickades till funktionen. Om den får en träff så döljer den alla div:ar som har klassen "content" och skickar sedan vidare användaren till http://www.google.com
Därefter kommer en callback-funktion som används för att ta emot svaret från tjänsten som tar fram IP-adressen. Svaret är i json så den tar ut IP-adressen ur svaret och skickar den vidare till blockeringsfunktionen.
Allra sist kommer anropet till tjänsten som kollar upp IP-adressen på besökaren och genom att ange en callback-parameter så triggar den min callback-funktion som slutligen triggar blockeringsfunktionen.

Kända problem
Tjänsten för att kolla upp IP-adresser och sedan anropa min callback-funktion blir lätt överbelastad eftersom många använder den. Det som händer är att tjänsten ligger på Google App Engine och utvecklaren kör bara gratisvarianten vilket bara tillåter ett visst antal requester per dygn. När det inträffar så kommer blockeringsfunktionen att sluta fungera tills tjänsten får ny quota.

Min lösning på det är att tillhandahålla min egen tjänst skriven i PHP. Den är inte publik och om jag släpper URL:en dit så riskerar den att bli överbelastad. Det är enkelt att skapa det själv med följande PHP-kod:
http://pastie.org/3301306 (Klicka på länken och hämta koden från Pastie)

PHP-skriptet skickar först ut headers som anger att det är en response i JSON och att inget ska cachas. Sedan plockar det ut IP-adress, hostname och eventuell callback-parameter och skriver ut det i JSON-format. Om en callback-parameter är angiven så läggs JSON-svaret som ett argument till angiven funktion. Det gör att när man bäddar in skriptet i en webbsida så kommer den automatiskt anropa callback-funktionen.

2012-01-23

Portable Apps

Portable Apps
Keep your favorite apps with you at all times and run them from any computer without the need to install them.
Portable Apps web site




How to install Portable Apps in an encrypted volume on a USB drive

Setup TrueCrypt volume

  1. Download and install Truecrypt: http://www.truecrypt.org/
    Choose Extract mode
    Select the USB drive as location.
  2. Start TrueCrypt.exe
  3. Press Create Volume and choose (use default values when you don't know what to choose):
    Create an encrypted file container
    Standard TrueCrypt volume
    Press Select File... and browse to the USB Drive and choose a name for the volume, for example: truecrypt_volume
    Set Volume Size to wanted size. Save some space for non encrypted files. Also consider the time it will take to move the volume between disks when you do backups
    Choose Yes for Large file storage
    Format the volume
  4. Create Traveler Disk Setup
    Choose Tools->Traveler Disk Setup
    Create traveler disk files in the root of the USB Drive
    Do not include TrueCrypt Volume Creation Wizard unless you need to create new volumes on the go
    In Autorun Configuration upon insertion of traveler disk "Auto-mount TrueCrype volume"
    In Mount Settings choose a suitable drive letter
  5. Mount the volume and continue with the setup for Portable Apps

Setup Portable Apps

  1. Download Portable Apps: http://portableapps.com/download
  2. Install Portable Apps on the mounted volume
  3. Start Portable Apps from the mounted volume (it should autostart)
  4. Press Manage Apps and choose the apps to install
    Recommended appsDevelopment:  Database Browser,  Notepad++Graphics and pictures:  GIMP, Inkscape, BlenderInternet:  Chrome, Firefox, Filezilla,  Pidgin, Skype, uTorrent, PuTTY, WinSCPMusic and video: VLCSecurity: KeePassUtilities: 7-Zip, TeamViewer, WinMerge

Mount encrypted volume in computer to prevent latency
A USB Drive often has a lower data rate than the hard drive so you might experience some latency running apps from the USB Drive. A way to prevent latency but still get the benefits of Portable Apps is to move the encrypted volume with Portable Apps to the computer and mount it directly from a folder.
Benefits
  • Decrease latency
  • Backup of your encrypted volume
Drawbacks
  • When you update Portable Apps with new apps or change settings in your current apps you need to move back the changed volume and update all local copies. It is just an easy copy-paste operation, but easy to forget.

2012-01-11

Uppgradera Nexus S till ICS (Android 4.0.3)

Nu tröttnade jag på att vänta på en OTA-uppdatering och gjorde ett försök att lägga på ICS, dvs. Android 4.0.3. Värt att nämna är att jag köpte min Nexus S i USA, men jag tror inte att något är annorlunda med den svenska varianten.

  1. Gör en backup av sånt du vill ha kvar + en kopia på hela SD-kortet/USB Storage
  2. Om du har 2-stegsverifiering kan det vara smart att stänga av det. Jag fick använda en av mina utskriva backupkoder för att komma in i telefonen efter uppgraderingen.
  3. Gör sedan en factory reset. Annars kommer mycket att strula vid uppstart av ICS. Jag gjorde en total factory reset med tömning av USB Storage efter att jag uppgraderat, men bättre att göra det innan så slipper ni allt strul.
  4. Ladda hem OTA-paketet från Google: länk (Det är version 4.0.3, 128 MB)
  5. Döp om filen till update.zip för enkelhetens skull
  6. Koppla telefonen till din dator och slå på USB storage
  7. Flytta update.zip till telefonen
  8. Slå av USB storage och stäng av telefonen helt (power off)
  9. Håll in Volym upp-knappen och slå på den igen
  10. Använd volym-knapparna och markera "recovery", gör valet med Power-knappen, nu startar telefonen om igen
  11. När varningstriangeln och Androiden dyker upp så håller du in Power-knappen och trycker Volym upp
  12. I menyn som dyker upp väljer du "apply update from /sdcard" med volym-knapparna och trycker på Power-knappen
  13. Välj update.zip från listan med filer och tryck återigen på Power-knappen
  14. Vänta och hoppas på det bästa....
  15. Klart! Det tog 3 minuter.
  16. Välj "reboot system now" och tryck på Power-knappen.
  17. Vänta på att rebooten ska bli klar...ny häftig boot-animation...flera upgrade-steg...som nog tog uppåt 10 minuter
  18. Klart!
De flesta av mina gamla appar återinstallerades. Så jag fick börja med att göra en storstädning av vad jag hade installerat. Funktionaliteten i en del gamla appar är numera inbyggd i operativsystemet så de behövs inte längre. Sen ville jag få en fräsch start så jag avinstallerade allt som jag inte använt på ett par veckor.

Första intrycket av Ice Cream Sandwich är att det känns väldigt fräscht och fungerar bra. Det kommer nog att ta några dagar innan jag vant mig vid de funktioner som flyttats runt, men allt känns välgjort och genomtänkt.