Monday, August 17, 2009

Referring to Android Resources Using URIs

In addition to loading Android resources using the Resource manager, you can also reference resources using a specially-formatted URI. Resource URIs can be referenced by resource type/name or by resource identifier. This can be especially useful if you are using a control like a VideoView which takes either a file path or a URI for the video source.

For example, let's say we have a VideoView and we want to load a resource from the raw resources called myvideo.3gp. We could construct a URI in two ways:

Using the resource id, the format is:

"android.resource://[package]/[res id]"

Uri path = Uri.parse("android.resource://com.androidbook.samplevideo/" + R.raw.myvideo);

or, using the resource subdirectory (type) and resource name (filename without extension), the format is:

"android.resource://[package]/[res type]/[res name]"

Uri path = Uri.parse("android.resource://com.androidbook.samplevideo/raw/myvideo");


This Uri can then be used to source the VideoView as follows:

VideoView myVid = (VideoView) findViewById(R.id.VideoView1);
myVid.setVideoURI(path);


You can now refer to any resource by URI.

14 comments:

Simuloid said...

When you specify the resource by numeric ID, and then concatenate it to the path string, are you sure it is supposed to be in decimal format rather than hex?

I can't seem to get any of these formats to work for MediaPlayer.create(context, Uri).

Just curious.

Shane Conder said...

Simuloid:

Concatenating the string with the resource id returned by the R class has always worked for us to reference the resources. Making sure the Uri and the resource it's pointing to are something that can be used by whatever method you're passing it to is different.

For instance, for a notification sound, I can do:

notify.sound = Uri.parse("android.resource://com.sample.package/"+R.raw.noise);

Simuloid said...

Thanks, Shane.
Sadly, it is still not working for me. Here is my code:

package com.europasw.examples;

import android.app.Activity;
import android.app.AlertDialog;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;

/**
*
* @author stever
*/
public class SoundTest extends Activity {

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
try
{
MediaPlayer mp = null;

if (mp == null)
{
// 1 Uri uri = Uri.parse("android.resource://com.europasw.examples/raw/dice_land.ogg");
Uri uri = Uri.parse("android.resource://com.europasw.examples/" + R.raw.dice_land );
// 2 Uri uri = Uri.parse("file:///sdcard/land.ogg");
MessageBox(uri.toString());
mp = MediaPlayer.create(this, uri);
}

if (mp != null)
{
mp.start();
}
else
{
MessageBox("No MP");
}
}
catch (Exception x)
{
MessageBox(x.toString());
}
}

void MessageBox(String msg)
{
AlertDialog.Builder builder =
new AlertDialog.Builder(SoundTest.this);
builder.setMessage(msg);
builder.setPositiveButton("OK", null);
AlertDialog d = builder.show();
}


}


I have tried using two different formats for the URI, and the URI parses fine, it just doesn't find the resource. I have tried specifying the sound resource with and without the .ogg extension.

The MessageBox pops up and shows me a "reasonable" uri, but then the mediaplayer is null.

Note that the version that gets the file from the SDCARD works fine (mp is not null and the sound plays).

Shane Conder said...

Hi Simuloid:

I don't see anything obviously wrong with your code. I recreated it, as follows:


package com.mamlambo.soundtest;

import android.app.Activity;
import android.app.AlertDialog;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;

public class SoundTestActivity extends Activity {

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
try {
MediaPlayer mp = null;

if (mp == null) {
Uri uri = Uri.parse("android.resource://com.mamlambo.soundtest/"
+ R.raw.fallbackring);
MessageBox(uri.toString());
mp = MediaPlayer.create(this, uri);
}

if (mp != null) {
mp.start();
} else {
MessageBox("No MP");
}
} catch (Exception x) {
MessageBox(x.toString());
}
}

void MessageBox(String msg) {
new AlertDialog.Builder(this)
.setMessage(msg)
.setPositiveButton("OK", null)
.show();
}

}

This works for me. If you want, I can provide the entire project for download so you can see if there might be something else that is different.

I do have a question and comment:

1. What version of the SDK are you using?
2. The purpose of Builders is to be able to chain calls. See how I changed around your MessageBox() method. It wasn't wrong, but the code is shorter here.

I hope this helps some. Again, if you'd like the whole project, I'll provide it as a download. My guess is that something else is different.

Simuloid said...

Thanks, Shane. I'm glad to know that it at least looks right. I am using NetBeans 6.8 with the NBAndroid plugin. I am using Android SDK Rev 6, and if it matters, I am targeting version 1.6 (I have a MyTouch 3G).
If I look into the APK (by changing the extension to ZIP and opening it), I can see my sound file in what appears to be the correct directory.

As for using Builders, I don't like the coding style of using them. Looks too much like Javascript. :-p

Sandor said...

Hi Simuloid:

Try taking off the ".ogg" extension and it should work I think.

Simuloid said...

Thanks Sandor, but removing the extension didn't work. Finally, somebody has posted a bug on the NBAndroid forum indicating the problem. It seems to have to do with how the NBAndroid ANT task is packaging up RAW resources.

When I build my project using Eclipse, it works fine.

Here is a bit of discussion about it from the nbandroid developer lists:

http://kenai.com/jira/browse/NBANDROID-50

There is also a work-around listed.

daniele said...

Hi, I have a problem with Uris:
This is the code:

public class Provafoto extends Activity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ImageView iv = (ImageView) findViewById(R.id.imm);
String nameFile = "poi18_image";


Uri path = Uri.parse("android.resource://provafoto.prova/raw/" + nameFile);
iv.setImageURI(path);


}
}

Now, when I run the code on the emulator it works, wheh i run it on an HTC Wildfire it doesn't work...

any idea? Thanks

daniele said...

About the Uri thing, that worked on my emulator but not on the htc wildfire, well it seems that the problem is on the version of Android: on my emulator worked because I used a 2.2 device, the htc wildfire has android 2.1

I made an emulated device version 2.1 and the code didn't work as on the wildfire.

Bye

Roger L. Cauvin said...

This gets really interesting now that newer versions of the SDK include the javax.xml.transform package. XML and XSLT typically make use of traditional filenames as identifiers. But it becomes tricky to reuse existing XML files and XSLT stylesheets from non-Android projects when they are stored as raw resources in the Android environment and must be referenced by resource ID.

For example, in an existing XSLT library of stylesheets, a stylesheet might include or reference another stylesheet or XML file by its filename. It won't work when doing transforms in Android without writing a special URIResolver.

I wonder how resource URIs fit into this picture. Are they part of the solution to the problem of reusing existing stylesheets and adapting them to the Android environment?

Baboon said...

Thanks a lot, this post helped :-)

LegendMVB said...
This comment has been removed by the author.
peter said...

Hi Shane i'm a student studying computer ingeneering and a beginer in android development.

i read your book (Teach Yourself Android Application Development in 24 Hours) and i just want your help in changing the code of (handleAnswerAndShowNextQuestion) to make the android OS increase the score of the game when the user presses the button no and not only the button yes, like the case of quiz questions game when the player presses the button true or false.
Here the code i used :

private void handleAnswerAndShowNextQuestion(boolean bAnswer) {


int curScore =
mGameSettings.getInt(GAME_PREFERENCES_SCORE, 0);

int nextQuestionNumber = mGameSettings.getInt(GAME_PREFERENCES_CURRENT_QUESTION, 1) + 1;

Editor editor =
mGameSettings.edit();
editor.putInt(GAME_PREFERENCES_CURRENT_QUESTION, nextQuestionNumber);

if (bAnswer == false && nextQuestionNumber == 1) {
editor.putInt(GAME_PREFERENCES_SCORE, curScore + 10);
editor.commit();}



if (bAnswer == false && nextQuestionNumber == 2) {
editor.putInt(GAME_PREFERENCES_SCORE, curScore + 10);
editor.commit();
}


if (bAnswer == true && nextQuestionNumber == 3) {
editor.putInt(GAME_PREFERENCES_SCORE, curScore + 10);
editor.commit();
}


if (bAnswer == true && nextQuestionNumber == 4) {
editor.putInt(GAME_PREFERENCES_SCORE, curScore + 10);
editor.commit();
}


if (bAnswer == false && nextQuestionNumber == 5) {
editor.putInt(GAME_PREFERENCES_SCORE, curScore + 10);
editor.commit();
}


if (bAnswer == false && nextQuestionNumber == 6) {
editor.putInt(GAME_PREFERENCES_SCORE, curScore + 10);
editor.commit();
}

if (bAnswer == false && nextQuestionNumber == 7) {
editor.putInt(GAME_PREFERENCES_SCORE, curScore + 10);
editor.commit();
}

if (bAnswer == true && nextQuestionNumber == 8) {
editor.putInt(GAME_PREFERENCES_SCORE, curScore + 10);
editor.commit();
}

if (bAnswer == true && nextQuestionNumber == 9) {
editor.putInt(GAME_PREFERENCES_SCORE, curScore + 10);
editor.commit();
}


if (bAnswer == false && nextQuestionNumber == 10) {
editor.putInt(GAME_PREFERENCES_SCORE, curScore + 10);
editor.commit();
}


if (bAnswer == true && nextQuestionNumber == 11) {
editor.putInt(GAME_PREFERENCES_SCORE, curScore + 10);
editor.commit();
}


if (bAnswer == false && nextQuestionNumber == 12) {
editor.putInt(GAME_PREFERENCES_SCORE, curScore + 10);
editor.commit();
}


if (bAnswer == false && nextQuestionNumber == 13) {
editor.putInt(GAME_PREFERENCES_SCORE, curScore + 10);
editor.commit();
}


if (bAnswer == true && nextQuestionNumber == 14) {
editor.putInt(GAME_PREFERENCES_SCORE, curScore + 10);
editor.commit();
}


if (bAnswer == false && nextQuestionNumber == 15) {
editor.putInt(GAME_PREFERENCES_SCORE, curScore + 10);
editor.commit();
}

peter said...

i want to tell you that i successed to show the score of the game using this code:

import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.widget.*;


public class score extends JeuActivity{
/** Called when the activity is first created. */


SharedPreferences mGameSettings;



@Override

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.score);

mGameSettings = getSharedPreferences(Jeu_PREFERENCES, Context.MODE_PRIVATE);
ListView menuList = (ListView)findViewById (R.id.ListView_Menu);


Integer[] items = { mGameSettings.getInt(GAME_PREFERENCES_SCORE, 0)};
ArrayAdapter adapt = new ArrayAdapter(this,R.layout.menu_item,items);
menuList.setAdapter(adapt);




}



}