Search This Blog

Showing posts with label custom. Show all posts
Showing posts with label custom. Show all posts

07 July 2011

Altering system.img, userdata.img and persist.img

If you have started building the Android source tree yourself, you have undoubtedly wondered how to modify the image files.  In this post, we'll look specifically at system.img.  This is based on the work found here.

Let's pretend our directories are:
~/work/android: directory where you did your git, envbuild, lunch/choosecombo from.
~/work/tmp: empty directory for us to play in
~/bin: directory on your path

If you have already done a build, you will find out/target/product/generic/system.img.  This is a YAFFS2 file.

Preparation

In my case, I did not have a way to mount yaffs2 filesystems.  I am sure I could have done some insmods and such; but instead, I chose to just compile this unyaffs.

~/work/tmp$ wget http://unyaffs.googlecode.com/files/unyaffs.c
~/work/tmp$ wget http://unyaffs.googlecode.com/files/unyaffs.h
~/work/tmp$ gcc -o unyaffs unyaffs.c
~/work/tmp$ cp unyaffs ~/bin
~/work/tmp$ rm *




Extracting system.img

~/work/tmp$ cp ~/work/android/out/target/product/generic/system.img .
~/work/tmp$ mkdir tmp2
~/work/tmp$ cd tmp2
~/work/tmp/tmp2$ unyaffs ../system.img

((modify files... for example, etc/init.goldfish.sh))


Rebuilding system.img

~/work/tmp$ rm system.img
~/work/tmp$ ~/work/android/out/host/linux-x86/bin/mkyaffs2image tmp2 system.img

Now you should be able to use the new system.img with your emulator =)


userdata.img and persist.img

These are handled in exactly the same way, just change the filenames.

Altering ramdisk.img

If you have started building the Android source tree yourself, you have undoubtedly wondered how to modify the image files.  In this post, we'll look specifically at ramdisk.img. This is based on the work found here.

Let's pretend our directories are:
~/work/android: directory where you did your git, envbuild, lunch/choosecombo from.
~/work/tmp: empty directory for us to play in

If you have already done a build, you will find out/target/product/generic/ramdisk.img.  This is a gzipped cpio file.
Extracting ramdisk.img

~/work/tmp$ cp ~/work/android/out/target/product/generic/ramdisk.img ./ramdisk.cpio.gz
~/work/tmp$ gzip -d ramdisk.cpio.gz
~/work/tmp$ mkdir tmp2
~/work/tmp$ cd tmp2
~/work/tmp/tmp2$ cpio -i -F ../ramdisk.cpio
((modify files... for example, init.rc or init.goldfish.rc))

Rebuilding ramdisk.img

~/work/tmp/tmp2$ cpio -i -t -F ../ramdisk.cpio | cpio -o -H newc -O ../ramdisk2.cpio
~/work/tmp/tmp2$ cd ..
~/work/tmp$ gzip -c ramdisk2.cpio > ramdisk.img


Now you should be able to use the new ramdisk.img with your emulator =)

18 July 2010

Intent Filters

Let's start with a basic app:

F:\work> mvn archetype:generate -DarchetypeCatalog=http://kallisti.eoti.org:8081/content/repositories/snapshots/archetype-catalog.xml
2: http://kallisti.eoti.org:8081/content/repositories/snapshots/archetype-catalog.xml -> galatea-archetype (null)
Choose a number: : 2
groupId: org.eoti.android.tutorial.intent
artifactId: IntentTest
version: 1.0-SNAPSHOT
package: org.eoti.android.tutorial.intent

F:\work> cd IntentTest
F:\work\IntentTest> mvn clean install


Add this into your IntentTestActivity:
    public void customClickHandler1(View target)
    {
        startActivity(new  Intent(Intent.ACTION_VIEW, Uri.parse("custom1://localhost/param1/param2")));
    }
   
Add this into the end of your LinearLayout in main.xml:
    <Button
        android:text="Click Me #1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="customClickHandler1" />

Following this methodology, we can have custom methods rather than multiple if/else.


Create a new activity:
package org.eoti.android.tutorial.intent;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class SubActivity extends Activity
{
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.sub);
        ((TextView)findViewById(R.id.text)).setText(getClass().getSimpleName() + " loaded: " + getIntent().getData());
    }
}



And create the res\layout\sub.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:id="@+id/text"
        />
</LinearLayout>


And add it to the AndroidManifest.xml:
        <activity android:name=".SubActivity">
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:scheme="custom1" android:host="localhost" android:pathPattern="/param1/.*" />
            </intent-filter>
        </activity>


Redeploy (mvn clean install)

Ok, that all works well... let's try having different path prefixes...

Add another button:
Update main.xml:
    <Button
        android:text="Click Me #2"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="customClickHandler2" />

Update IntentTestActivity:
    public void customClickHandler2(View target)
    {
        startActivity(new  Intent(Intent.ACTION_VIEW, Uri.parse("custom1://localhost/paramA/paramB")));
    }

Create SubActivity2:
package org.eoti.android.tutorial.intent;

import android.os.Bundle;

public class SubActivity2 extends SubActivity
{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

Update AndroidManifest.xml:
        <activity android:name=".SubActivity2">
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:scheme="custom1" android:host="localhost" android:pathPattern="/paramA/.*" />
            </intent-filter>
        </activity>

Redeploy (mvn clean install)... Now, we can launch either one by the first part of the URL...  what about something at the end?


Make a new activity:
package org.eoti.android.tutorial.intent;

import android.os.Bundle;

public class SubActivity3 extends SubActivity
{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

        <activity android:name=".SubActivity3">
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:scheme="custom1" android:host="localhost" android:pathPattern="/paramA/.*/paramC" />
            </intent-filter>
        </activity>

    <Button
        android:text="Click Me #3"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="customClickHandler3" />

    public void customClickHandler3(View target)
    {
        startActivity(new  Intent(Intent.ACTION_VIEW, Uri.parse("custom1://localhost/paramA/paramB/paramC")));
    }

Redeploy (mvn clean install)... what happens?  #1 and #2 work; but #3 asks you how you want to handle it (the picker is making you choose between #2 and #3).  That's because the 3rd url matches both intent-filters.  How do we fix that?

We don't have access to the full regex library (almost none of it really), so the simplest way is to make it behave more like files and directories...

Let's make "/paramA/paramB" more like a directory by appending a slash (ie: "/paramA/paramB/")
    public void customClickHandler2(View target)
    {
        startActivity(new  Intent(Intent.ACTION_VIEW, Uri.parse("custom1://localhost/paramA/paramB/")));
    }

        <activity android:name=".SubActivity2">
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:scheme="custom1" android:host="localhost" android:pathPattern="/paramA/.*/" />
            </intent-filter>
        </activity>

Redeploy (mvn clean install). Now what happens? All three of them now launch.  We have a gotcha here though.  Try this:

    <Button
        android:text="Broken"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="customClickHandler4" />

    public void customClickHandler4(View target)
    {
        startActivity(new  Intent(Intent.ACTION_VIEW, Uri.parse("custom1://localhost/paramA/paramB")));
    }

Redeploy (mvn clean install) and you will see that clicking the "Broken" button will cause a stack trace:
E/AndroidRuntime( 1278): Caused by: android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW dat=custom1://localhost/paramA/paramB }
E/AndroidRuntime( 1278):        at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:1408)
E/AndroidRuntime( 1278):        at android.app.Instrumentation.execStartActivity(Instrumentation.java:1378)
E/AndroidRuntime( 1278):        at android.app.Activity.startActivityForResult(Activity.java:2817)
E/AndroidRuntime( 1278):        at android.app.Activity.startActivity(Activity.java:2923)
E/AndroidRuntime( 1278):        ... 15 more
W/ActivityManager(   61):   Force finishing activity org.eoti.android.tutorial.intent/.IntentTestActivity

This is because we now require a slash to be on the end (or 'paramC').

Let's try one more thing:

    <Button
        android:text="Wrong"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="customClickHandler5" />

    public void customClickHandler5(View target)
    {
        startActivity(new  Intent(Intent.ACTION_VIEW, Uri.parse("custom1://localhost/paramA/paramB/paramC/paramD/")));
    }

Redeploy (mvn clean install).  What happens?  Why doesn't it match "/paramA/.*/" from SubActivity2?  My only guess is that the "everything" in ".*" excludes the "/"?

So end result?  If you are willing to carefully layout your Uris and be limited to ".*" or literals (rather than say '[0-9]+') then this solution MIGHT be cleaner than creating an intent hub to process the strings and start your activities...  too bad we don't have access to true Java regex functionality.

28 May 2010

Customizing the Views

Let's start by creating a basic class:
F:\work> mvn archetype:generate -DarchetypeCatalog=http://kallisti.eoti.org:8081/content/repositories/snapshots/archetype-catalog.xml
Choose the galatea-archetype plugin
groupId: org.eoti.android
artifactId: CustomViewTest
version: 1.0-SNAPSHOT
package: org.eoti.android


Assuming your emulator is running...
F:\work> cd CustomViewTest
F:\work\CustomViewTest> mvn clean install

Create a new class.  In my case I created src\main\java\org\eoti\android\CustomTextView.java

public class CustomTextView
extends TextView
{
    public CustomTextView(Context context) {
        super(context);
    }

    public CustomTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        Paint cornsilk = new Paint(Paint.ANTI_ALIAS_FLAG);
        cornsilk.setColor(Color.rgb(255,248,220));

        canvas.drawLine(0, 0, this.getMeasuredWidth(), this.getMeasuredHeight(), cornsilk);
        canvas.drawLine(this.getMeasuredWidth(), 0, 0, this.getMeasuredHeight(), cornsilk);
       
        super.onDraw(canvas);
    }
}


Update src\main\android\res\layout\main.xml
Replace this line:
    <TextView 
with this line:
    <org.eoti.android.CustomTextView

Redeploy (mvn clean install) and run the app. You should see lines behind the text.