Friday, February 29, 2008

Ubuntu goes Mobile

This is interesting. Just when we thought Android was 'The' linux flavour on mobile, Ubuntu comes along with even a "better looking" platform, Ubuntu Mobile Edition(UME). The platform is being projected for Mobile Internet Devices (MID) with the main focus on bringing complete internet to mobile devices. The specs mentioned are very impressive,

  • Full Web 2.0/AJAX fidelity, with custom options of Adobe Flash®, Java, and more
  • Outstanding media playback so you can enjoy videos, music and photos with superior quality and easy navigation
  • A suite of applications that work seamlessly to meet every need of a digital parent, student or anyone who is on-the-go
  • Facebook®, MySpace®, YouTube®, Dailymotion®, 3D games, GPS, maps, in short, the full Web 2.0 experience delivered into your hands as a compact and powerful device that's easy and fun to use.

Like Android, UME will be a customizable software stack supplied to OEMs, for example the user interface can be HTML, Flash, Java or C/C++/Python with GTK. Currently, the target is mainly Intel's MID (Mobile Internet Device) platforms, code-named McCaslin and Menlow like HTC Shift and Samsung Q1 Ultra among others.

UME will have full mobile connectivity in Bluetooth, GPS, Wi-Fi, Wi-Max, VoIP, USB. It is not yet clear if it will have a dialer application, but looking at the specs, it wouldn't hurt to incorporate a GSM radio chip in it.

Now, if UME is going so serious about Web 2.0 applications, where does it leave Android.

With all this Flash interface and fire power, does it make Android look less pretty ? take a look at the pics.








Thursday, February 28, 2008

Latest Android demo video

Darren Waters of BBC News met Andy Rubin,Google's director of mobile platforms, on Feb 26 for a round of interview. The demo video shows quite a few apps installed. Rubin shows the device's 3G and 3D gaming capabilities. The demo device operates pretty fast considering it has a 300 MHZ(approx.) chip.The interview is kind of a reiteration of Google's take on mobile platform and the usual comparison with Windows Mobile and Symbian thrown in. The demo video is at his blog here and his interview with Andy Rubin here.

Wednesday, February 27, 2008

Searching in a local database

SDK : M5-rc14


Previously, we searched in an array, now we are going to do that in a database. I'll be using the image viewing application from previous posts. The steps are,

a. Create a database in Images.java and insert the name and path of images.

b. Access the database in ImageSearch.java and retrieve the values.



Images.java

public SQLiteDatabase db = null;
public static String dbase = "imagedb";
static String dbTable = "my_table";

try {

createDatabase(dbase, 1, MODE_WORLD_READABLE,null);
db = openDatabase(dbase, null);


} catch (FileNotFoundException e)
{
e.printStackTrace();
}

db.execSQL("CREATE TABLE IF NOT EXISTS "+dbTable+ " (Name TEXT, Path TEXT);");

for(int i= 0 ; i< imagelist.length; i++)
{
// mFiles[i] = imagelist[i].getAbsolutePath();

db.execSQL("INSERT OR IGNORE INTO " + dbTable + " VALUES ('"+ imagelist[i].getName() + "', '" + imagelist[i].getAbsolutePath() +"');");
}

- Nothing much to explain here. They are the basic SQLite statements.




ImageSearch.java

public SQLiteDatabase db = null;

protected void onCreate(Bundle icicle)
{

super.onCreate(icicle);
try
{
db = this.openDatabase(Images.dbase, null);
}catch(FileNotFoundException e)
{
e.printStackTrace();
}


- Open the database in onCreate().



Remove/Comment the following code in doSearchQuery(). Thats the array part.

for(String file : Images.mFiles)
{
if(file.contains(queryString))
{
result.add(file);
}
}


And add the following code after queryString in doSearchQuery() method.

String[] values = {"Name", "Path"};
Cursor c = db.query(true, Images.dbTable, values, null, null, null, null, null);


int name = c.getColumnIndex("Name");

int path = c.getColumnIndex("Path");

if (c != null)
{
if (c.first())
{
do {
String imagename = c.getString(name);
String imagepath = c.getString(path);

if(imagepath.contains(queryString))
{
result.add(imagepath);
}
} while (c.next());
}
}


- We'll query the database for all the names and paths. But we are not using the name.

- Add the matching paths to the result ArrayList and pass it on to the Uri array like before.


These are the only differences between searching in an array and a database.

Tuesday, February 26, 2008

Implementing Search

SDK : M5-rc14

We'll implement a Search feature into the image viewing application we built previously. Ideally, this Search should have been implemented using Filter-Search, but due to some bugs in it, we'll be using Query-Search.

AndroidManifest.xml
<activity android:name=".Images" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <meta-data android:name="android.app.default_searchable" android:value=".app.ImageSearch" />
</activity>

- Add the meta-data tag to the Image activity declaration.

- Detail explanation for this and all following tags is available in the documentation. Simply put, whenever a search is launched in this activity, ImageSearch will be the default class to handle the search.



<activity android:name="ImageSearch" android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="android.app.searchable" android:resource="@xml/searchable" />
</activity>


- Add the ImageSearch activity declaration. This activity will receive and handle the search queries.



searchable.xml

<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:searchLabel="Images" />

- Add this file to res/xml/ .



Images.java

public boolean onSearchRequested() { startSearch(null, null); return true; }

- This is the only piece of extra code that we'll be adding to the existing Images.java




ImageSearch.java


protected void onCreate(Bundle icicle)
{

super.onCreate(icicle);
final Intent queryIntent = getIntent();
final String queryAction = queryIntent.getAction();
if (Intent.SEARCH_ACTION.equals(queryAction))
{
doSearchQuery(queryIntent, "onCreate()");
}

setContentView(R.layout.searchresult);
GridView g = (GridView) findViewById(R.id.myGrid);
g.setAdapter(new ImageAdapter(ImageSearch.this));
}


- The if construct will check if the intent recieved is SEARCH_ACTION.

- The search results will be shown as a GridView of images.




public void onNewIntent(final Intent newIntent)
{
super.onNewIntent(newIntent);
final Intent queryIntent = getIntent();
final String queryAction = queryIntent.getAction();
if (Intent.SEARCH_ACTION.equals(queryAction))
{
doSearchQuery(queryIntent, "onNewIntent()");
}
}


- Not used. Included only for explanation.

-
This method should be used in two situations,

a. If the search results activity(ImageSearch.java in this case) should also invoke a search.

b. for implementing Filter-Search.




private void doSearchQuery(final Intent queryIntent, final String entryPoint)
{
result = new ArrayList<String>();

queryString = queryString + queryIntent.getStringExtra(SearchManager.QUERY);

setTitle("Search results for "+"'"+queryString+"'");

for(String file : Images.mFiles)
{
if(file.contains(queryString))
{
result.add(file);
}
}

mUris = new Uri[result.size()];

for(int i=0; i < result.size(); i++)
{
mUris[i] = Uri.parse(result.get(i));
}
}



- In this method, we will use the query we received to select the results from the images stored in mFiles array.

- queryString will store the user-query.

- result ArrayList will store paths of all the images that match the query.

- From the result ArrayList, paths will be passed on to the Uri array and then to the ImageAdapter, like in the case of Images.java.


ImageSearch.java consists of some more methods and classes , but they are similar to the View constructed in Images.java, except, there we used Gallery and here we are using GridView.



Source :


Code for Images.java remains the same except for the small method as mentioned above.


AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="tp.proj">
<application android:icon="@drawable/icon">
<activity android:name=".Images" android:label="@string/app_name" >
<intent-filter>

<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data android:name="android.app.default_searchable"
android:value=".app.ImageSearch" />
</activity>

<!-- Search Activity -->
<activity android:name="ImageSearch"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="android.app.searchable"
android:resource="@xml/searchable" />
</activity>
</application>
</manifest>





res/xml/searchable.xml

<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android" android:searchLabel="Images"/>



searchresult.xml (Layout for ImageSearch)

<GridView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/myGrid"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10dip"
android:verticalSpacing="10"

android:horizontalSpacing="10"
android:numColumns="4"
android:columnWidth="60"
android:stretchMode="columnWidth"

android:gravity="center"
/>



ImageSearch.java

public class ImageSearch extends Activity
{
String queryString = "";
ArrayList<String> result;
String oldQuery=null;
String[] resultarray;
private Uri[] mUris;
protected void onCreate(Bundle icicle)
{

super.onCreate(icicle);
final Intent queryIntent = getIntent();
final String queryAction = queryIntent.getAction();
if (Intent.SEARCH_ACTION.equals(queryAction))
{
doSearchQuery(queryIntent, "onCreate()");
}

setContentView(R.layout.searchresult);
GridView g = (GridView) findViewById(R.id.myGrid);
g.setAdapter(new ImageAdapter(ImageSearch.this));

}

public View makeView()
{
ImageView i = new ImageView(this);
i.setBackgroundColor(0xFF000000);
i.setScaleType(ImageView.ScaleType.CENTER_CROP);
i.setLayoutParams(new LayoutParams(80,80));
return i;
}

@Override
public void onNewIntent(final Intent newIntent)
{
super.onNewIntent(newIntent);

final Intent queryIntent = getIntent();
final String queryAction = queryIntent.getAction();
if (Intent.SEARCH_ACTION.equals(queryAction))
{
doSearchQuery(queryIntent, "onNewIntent()");
}
}

public class ImageAdapter extends BaseAdapter
{

public ImageAdapter(Context c)
{
mContext = c;
}

public int getCount()
{
return mUris.length;
}

public Object getItem(int position)
{
return position;
}

public long getItemId(int position)
{
return position;
}

public View getView(int position, View convertView, ViewGroup parent)
{
ImageView i = new ImageView(mContext);

i.setImageURI(mUris[position]);
i.setLayoutParams(new Gallery.LayoutParams(120, 100));
i.setAdjustViewBounds(false);
i.setScaleType(ImageView.ScaleType.FIT_XY);
i.setPadding(8, 10, 16, 8);
return i;
}

private Context mContext;

}

private void doSearchQuery(final Intent queryIntent, final String entryPoint)
{
result = new ArrayList<String>();

queryString = queryString + queryIntent.getStringExtra(SearchManager.QUERY);

setTitle("Search results for "+"'"+queryString+"'");

for(String file : Images.mFiles)
{
if(file.contains(queryString))
{
result.add(file);
}
}

mUris = new Uri[result.size()];

for(int i=0; i < result.size(); i++)
{
mUris[i] = Uri.parse(result.get(i));
}

}
}


Displaying Images (Updated to 0.9-r1)

SDK: 0.9-r1

Usually the images are stored in the application's resources. In some cases, images that are stored at different locations in the system need to be accessed. We'll be using Gallery to view the images stored in SD card.














Layout XML :

  <Gallery xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/gallery"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:gravity="center_vertical"
android:spacing="7px" />
- Gallery is the only object needed in the XML file.

- android:spacing controls the distance between center and left/right images.




Images.java :

public class Images extends Activity
{
private Uri[] mUrls;
String[] mFiles=null;

public void onCreate(Bundle icicle)
{

super.onCreate(icicle);
setContentView(R.layout.main);

class ImageFilter implements FilenameFilter
{
public boolean accept(File dir, String name)
{
return (name.endsWith(".jpg"));
}
}

File images = new File("/sdcard/");
File[] imagelist = images.listFiles(new ImageFilter());

mFiles = new String[imagelist.length];

for(int i= 0 ; i< imagelist.length; i++)
{
mFiles[i] = imagelist[i].getAbsolutePath();
}

mUrls = new Uri[mFiles.length];

for(int i=0; i < mFiles.length; i++)
{
mUrls[i] = Uri.parse(mFiles[i]);
}

- mUrls array stores the path of the images that are parsed from mFiles array.


Gallery g = (Gallery) findViewById(R.id.gallery);
g.setAdapter(new ImageAdapter(this));
//g.setSelectorSkin(getResources().getDrawable(R.drawable.gallery_background_1));

- The ImageAdapter object returns a list of images.


public class ImageAdapter extends BaseAdapter
{
public ImageAdapter(Context c)
{
mContext = c;
}

public int getCount()
{
return mUrls.length;
}

public Object getItem(int position)
{
return position;
}

public long getItemId(int position)
{
return position;
}

public View getView(int position, View convertView, ViewGroup parent)
{
ImageView i = new ImageView(mContext);

i.setImageURI(mUrls[position]);
i.setScaleType(ImageView.ScaleType.FIT_XY);
i.setLayoutParams(new Gallery.LayoutParams(260, 250));
return i;
}

- ImageAdapter is a custom class derived from BaseAdapter to setup the Gallery view. It provides the Gallery values like no. of items in the gallery, their position etc.

- getView
method sets up the image currently at the center.

- A new ImageView is created which sets the address to the path stored earlier in mUrls array.

- The size of the image is decided by setScaleType. When it is set to FIT_XY, it takes the X and Y values from the LayoutParams defined below it (260x250 in this case).



public float getAlpha(boolean focused, int offset)
{
return Math.max(1.0f, 1.0f - (0.2f * Math.abs(offset)));
}

public float getScale(boolean focused, int offset)
{
return Math.max(0.5f, 1.0f - (0.25f * Math.abs(offset)));
}
- getAlpha method controls the translucency at the corner of the left and right images (highlighted in red below).


- getScale
method controls the size of the left and right images.



SQLite Basics (Updated to 0.9-r1)

SDK : 0.9-r1

If this is your first encounter with SQLite, consider spending some time here.We'll do a simple activity to list some data stored in the database. There are some changes in the new SDK highlighted in blue.













SQLite.java :

public class SQLite extends ListActivity
{
public static SQLiteDatabase db = null;
String dbase = "myDB";
String dbTable = "my_table";
ArrayList<String> result = new ArrayList<String>();

public void onCreate(Bundle icicle)
{
super.onCreate(icicle);
setContentView(R.layout.main);

try
{

db = openOrCreateDatabase(dbase, MODE_WORLD_READABLE,null);

} catch (FileNotFoundException e)
{
e.printStackTrace();
}
- createDatabase() and openDatabase() methods have been replaced by openOrCreateDatabase() method.

- db manages all the queries and SQL commands.

- Both createDatabase and openDatabase throw FileNotFoundException if the database could not be created/opened.


db.execSQL("CREATE TABLE IF NOT EXISTS "+dbTable+ " (Company TEXT, Model TEXT);");
db.execSQL("INSERT OR IGNORE INTO " + dbTable + " VALUES ('Motorola', 'Z12');");
db.execSQL("INSERT OR IGNORE INTO " + dbTable + " VALUES ('Nokia', 'N96');");
db.execSQL("INSERT OR IGNORE INTO " + dbTable + " VALUES ('HTC', 'Touch');");

- The execSQL method is used in non-query statements like DELETE, INSERT etc.

- In the INSERT statement, we input the value to be stored. However, in most conditions, the values will be stored in some variables. In such cases we use,db.execSQL("INSERT OR IGNORE INTO " + dbTable + " VALUES ('"+ company_value1 + "', '" + model_value1 +"');");


String[] values = {"Company", "Model"};
Cursor c = db.query(true, dbTable, values, null, null, null, null, null,null);
- An extra parameter has been added to the query() method. You can now limit the no. of rows to be queried.

- Cursor provides read/write access to the result from the query.

- values stores all the tables you want to query.


// Get the indices of the columns

int country = c.getColumnIndexOrThrow("Company");
int model = c.getColumnIndexOrThrow("Model");

// Check if any data was returned.
if (c != null)
{
if (c.first())
{
int i = 0;

// Loop through all results

do {
i++;

// Get values of the row the Cursor is pointing to.

String countryname = c.getString(country);

String modelname = c.getString(model);

// Add the value to an ArrayList<String> result

result.add("" + i + ". " + countryname+ " - " + modelname);

} while (c.next());
}
}

ArrayAdapter<String> fileList = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, result);

setListAdapter(fileList);

- getColumnIndex() has been replaced by getColumnIndexOrThrow() method.

- The results we store in the result list is added to the ArrayAdapter.

- ArrayAdapter manages the ListView which we use to display our data.


Sunday, February 24, 2008

Splashscreen (Updated to 0.9-r1)

SDK : 0.9-r1

The basic logic is to use a thread to do all the background work while the image is being displayed. There are no changes in this code as far as the new SDK is concerned.






























public class Splashscreen extends Activity

{

ImageView splash;

final Handler mHandler = new Handler();


public void onCreate(Bundle icicle)
{
super.onCreate(icicle);

setContentView(R.layout.main);

splash = (ImageView) findViewById(R.id.splash);

Thread t = new Thread()
{
public void run()
{
mHandler.postDelayed(update, 4000);
}


};
t.start();
}



  • mHandler is used to send the runnable when our data processing is done.



final Runnable update = new Runnable()
{
public void run()
{
splash.setVisibility(View.GONE);
}

};

}

  • The update Runnable object will run when it receives the runnable from mHandler.

  • The above code takes-off the image after 4 seconds. But in most conditions, you would want to remove it as soon as the processing is done. For this, replace the postDelayed message with mHandler.post(update); In this case, if your application requires less processing during startup, the splashscreen will not be visible.


Saturday, February 23, 2008

Opening New Screen

SDK Version : 0.9-r1

This code allows calling other classes from a class (or opening a new screen). Useful for starting a new Activity like a new screen or dialog.



try
{
Intent i = new Intent(<this classname>.this, <class to be called>.class);
startActivity(i); // startSubActivity(i,0) will also work if you just want to open a dialog box.
}
catch (ActivityNotFoundException e)
{
showAlert("Error", "Activity not found", "OK", false);
}


Intent takes two parameters,

1. The current context of the package.

2. Class to be called.

You also need to declare the new class in the "AndroidManifest.xml" file. Following is a template code :

<activity name=".Classname" label="@string/title">
<intent-filter>
<!--– any permissions you want to assign to the new Class–-->
</intent-filter>
</activity>

Emulating SD card

Use mksdcard for creating a FAT32 image of a SD card. For example,

mksdcard 1024M sd_card

To make sure the image is used with the emulator, launch the emulator with -sdcard option.

On Eclipse, this can be done by going to “Run > Open Run Dialog”, in the “Target” tab,

add the option in the “Additional Emulator Command Line Options” :



For adding/removing files from the sd card, you can use the DDMS view in Eclipse or use the command ,

adb push /sdcard/