Search This Blog
03 July 2010
Ardor3d for Android
Ardor3d has done an alpha release for Android. Go check it out :) On this box example, I get a solid 60 fps on my NexusOne. I'm only getting about 12 fps on the emulator; but the emulator is always slow.
OTA FRF91
The other day, I got tired of waiting and manually upgraded my NexusOne to FRF83. This morning I was greeted by an OTA update to FRF91. I have not seen any changes (as of yet) but hear it has to do with GMail security. Personally I am still waiting for the FM Transmit so that I can actually hear the handsfree on the car dock.
01 July 2010
Content Provider and Live Folder
We're going to create a basic ContentProvider example based on this tutorial.
Let's start with a multi-module project:
Create a top-level pom.xml:
Open the TestProvider\pom.xml and TestConsumer\pom.xml and add a parent to the pom.xml:
Replace TestProviderActivity.java with TestProvider.java:
Change your TestProvider\AndroidManifest.xml:
Replace TestConsumerActivity.java with TestConsumer.java:
Change your TestConsumer\AndroidManifest.xml:
Update your TestGeneratorActivity:
Redeploy (mvn clean install). Launch the generator app. Add the live folder to your desktop. Launch it. Run the generator a few more times. Launch the folder again.
Let's start with a multi-module project:
F:\work> mkdir ProviderTest
F:\work> cd ProviderTest
F:\work\ProviderTest> 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)
Confirm properties configuration:
groupId: org.eoti.android.providertest.provider
artifactId: TestProvider
version: 1.0-SNAPSHOT
package: org.eoti.android.providertest.provider
F:\work\ProviderTest> 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)
Confirm properties configuration:
groupId: org.eoti.android.providertest.consumer
artifactId: TestConsumer
version: 1.0-SNAPSHOT
package: org.eoti.android.providertest.consumer
F:\work\ProviderTest> 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)
Confirm properties configuration:
groupId: org.eoti.android.providertest.generator
artifactId: TestGenerator
version: 1.0-SNAPSHOT
package: org.eoti.android.providertest.generator
Create a top-level pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.eoti.android.providertest</groupId>
<version>1.0-SNAPSHOT</version>
<artifactId>ProviderTest</artifactId>
<packaging>pom</packaging>
<name>ProviderTest</name>
<description>Testing a simple Content Provider and consumer</description>
<modules>
<module>TestProvider</module>
<module>TestConsumer</module>
<module>TestGenerator</module>
</modules>
</project>
Open the TestProvider\pom.xml and TestConsumer\pom.xml and add a parent to the pom.xml:
<parent>
<groupId>org.eoti.android.providertest</groupId>
<version>1.0-SNAPSHOT</version>
<artifactId>ProviderTest</artifactId>
</parent>
F:\work\ProviderTest> mvn clean install
Replace TestProviderActivity.java with TestProvider.java:
package org.eoti.android.providertest.provider;
import android.app.Activity;
import android.content.*;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.os.Bundle;
import android.provider.BaseColumns;
import android.provider.LiveFolders;
import android.text.TextUtils;
import android.util.Log;
import java.util.HashMap;
public class TestProvider
extends ContentProvider
{
// Derived from http://developer.android.com/resources/samples/NotePad/src/com/example/android/notepad/NotePadProvider.html
public static final String AUTHORITY = "org.eoti.android.providertest";
private static class Logs implements BaseColumns
{
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/logs");
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.eoti.logs";
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.eoti.log";
public static final String DEFAULT_SORT_ORDER = "modified DESC";
public static final String LOG = "log";
public static final String TIMESTAMP = "timestamp";
}
private static String TAG = "TestProvider";
private static final String DATABASE_NAME = "providertest.db";
private static final int DATABASE_VERSION = 1;
private static final String TABLE_NAME = "logs";
private static final int LOGS = 1;
private static final int LOG_ID = 2;
private static final int LIVE_FOLDER_LOGS = 3;
private static HashMap<String, String> logsProjectionMap;
private static HashMap<String, String> folderProjectionMap;
private static final UriMatcher uriMatcher;
private static class DatabaseHelper extends SQLiteOpenHelper
{
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + TABLE_NAME + " ("
+ Logs._ID + " INTEGER PRIMARY KEY,"
+ Logs.LOG + " TEXT,"
+ Logs.TIMESTAMP + " INTEGER"
+ ");");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(TAG, "Upgrading database from version " + oldVersion + " to " + newVersion + ", which will destroy all old data");
db.execSQL("DROP TABLE IF EXISTS notes");
onCreate(db);
}
}
private DatabaseHelper dbHelper;
@Override
public boolean onCreate() {
dbHelper = new DatabaseHelper(getContext());
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(TABLE_NAME);
switch (uriMatcher.match(uri)) {
case LOGS:
qb.setProjectionMap(logsProjectionMap);
break;
case LOG_ID:
qb.setProjectionMap(logsProjectionMap);
qb.appendWhere(Logs._ID + "=" + uri.getPathSegments().get(1));
break;
case LIVE_FOLDER_LOGS:
qb.setProjectionMap(folderProjectionMap);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
String orderBy = (TextUtils.isEmpty(sortOrder) ? Logs.DEFAULT_SORT_ORDER : sortOrder);
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case LOGS:
case LIVE_FOLDER_LOGS:
return Logs.CONTENT_TYPE;
case LOG_ID:
return Logs.CONTENT_ITEM_TYPE;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
}
@Override
public Uri insert(Uri uri, ContentValues initialValues) {
if (uriMatcher.match(uri) != LOGS)
throw new IllegalArgumentException("Unknown URI " + uri);
ContentValues values = (initialValues == null) ? new ContentValues() : new ContentValues(initialValues);
if(!values.containsKey(Logs.TIMESTAMP))
values.put(Logs.TIMESTAMP, System.currentTimeMillis());
if(!values.containsKey(Logs.LOG))
values.put(Logs.LOG, "");
SQLiteDatabase db = dbHelper.getWritableDatabase();
long rowId = db.insert(TABLE_NAME, Logs.LOG, values);
if (rowId > 0) {
Uri noteUri = ContentUris.withAppendedId(Logs.CONTENT_URI, rowId);
getContext().getContentResolver().notifyChange(noteUri, null);
return noteUri;
}
throw new SQLException("Failed to insert row into " + uri);
}
@Override
public int delete(Uri uri, String where, String[] whereArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int count;
switch (uriMatcher.match(uri)) {
case LOGS:
count = db.delete(TABLE_NAME, where, whereArgs);
break;
case LOG_ID:
String noteId = uri.getPathSegments().get(1);
count = db.delete(TABLE_NAME, Logs._ID + "=" + noteId + (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""), whereArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
@Override
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int count;
switch (uriMatcher.match(uri)) {
case LOGS:
count = db.update(TABLE_NAME, values, where, whereArgs);
break;
case LOG_ID:
String noteId = uri.getPathSegments().get(1);
count = db.update(TABLE_NAME, values, Logs._ID + "=" + noteId + (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""), whereArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
static{
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, "logs", LOGS);
uriMatcher.addURI(AUTHORITY, "logs/#", LOG_ID);
uriMatcher.addURI(AUTHORITY, "livefolders/logs", LIVE_FOLDER_LOGS);
logsProjectionMap = new HashMap<String,String>();
logsProjectionMap.put(Logs._ID, Logs._ID);
logsProjectionMap.put(Logs.LOG, Logs.LOG);
logsProjectionMap.put(Logs.TIMESTAMP, Logs.TIMESTAMP);
folderProjectionMap = new HashMap<String, String>();
folderProjectionMap.put(LiveFolders._ID, Logs._ID + " AS " + LiveFolders._ID);
folderProjectionMap.put(LiveFolders.NAME, Logs.LOG + " AS " + LiveFolders.NAME);
}
}
Change your TestProvider\AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.eoti.android.providertest.provider">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<provider android:name="TestProvider" android:authorities="org.eoti.android.providertest"/>
</application>
</manifest>
Replace TestConsumerActivity.java with TestConsumer.java:
package org.eoti.android.providertest.consumer;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.LiveFolders;
public class TestConsumer extends Activity
{
public static final String AUTHORITY = "org.eoti.android.providertest";
private static String TAG = "TestConsumer";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/livefolders/logs");
public static final Uri LOG_URI = Uri.parse("content://" + AUTHORITY + "/logs/#");
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
final Intent intent = getIntent();
final String action = intent.getAction();
if (LiveFolders.ACTION_CREATE_LIVE_FOLDER.equals(action))
{
final Intent liveFolderIntent = new Intent();
liveFolderIntent.setData(CONTENT_URI);
liveFolderIntent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_NAME, getString(R.string.app_name));
liveFolderIntent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_ICON, Intent.ShortcutIconResource.fromContext(this, R.drawable.icon));
liveFolderIntent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_DISPLAY_MODE, LiveFolders.DISPLAY_MODE_LIST);
liveFolderIntent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_BASE_INTENT, new Intent(Intent.ACTION_EDIT, LOG_URI));
setResult(RESULT_OK, liveFolderIntent);
} else {
setResult(RESULT_CANCELED);
}
finish();
}
}
Change your TestConsumer\AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.eoti.android.providertest.consumer">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name="TestConsumer" android:label="@string/app_name" android:icon="@drawable/icon">
<intent-filter>
<action android:name="android.intent.action.CREATE_LIVE_FOLDER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>
Update your TestGeneratorActivity:
package org.eoti.android.providertest.generator;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.net.Uri;
import android.os.Bundle;
import android.widget.Toast;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TestGeneratorActivity extends Activity {
public static final String AUTHORITY = "org.eoti.android.providertest";
private static String TAG = "TestGenerator";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/logs");
private static final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
/**
* Called when the activity is first created.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ContentValues values = new ContentValues();
long time = System.currentTimeMillis();
values.put("timestamp", time);
values.put("log", sdf.format(new Date(time)));
Uri uri = getContentResolver().insert(CONTENT_URI, values);
Toast.makeText(getApplicationContext(), (uri == null) ? " null " : uri.toString(), Toast.LENGTH_SHORT).show();
finish();
}
}
Redeploy (mvn clean install). Launch the generator app. Add the live folder to your desktop. Launch it. Run the generator a few more times. Launch the folder again.
27 June 2010
Manually installed FroYo
Ok, I got impatient. I have been checking my System Updates window a few times a day, and still no sign of the update... so I decided to do it manually using these instructions (recapped here):
*Note: These instructions are apparently for the unlocked TMobile version of the NexusOne (which I happen to be using on the AT&T network).
*Note: These instructions are apparently for the unlocked TMobile version of the NexusOne (which I happen to be using on the AT&T network).
- Verify that you are on build ERE27 (under Settings | About Phone)
- Download this file as 'update.zip' in the root of your microSD (which I did via the USB connection)
- Not sure if it is necessary, but I unplugged the USB for the next few steps
- Power off your phone
- Hold down the trackball and press/release the power button
- When the white screen with skateboarding robots show up, make sure Bootloader is selected (should be already) and hit the power button.
- If you try to scroll down (using Volume buttons) the screen may flash to another message for a moment. When it comes back, scroll down to Recovery and hit the power button.
- After it reboots, you'll see the exclamation screen.
- Press the power and volume up buttons. I found PressPower->Press/Release Volume->Release Power seemed to work
- Using the trackball, scroll down and select Apply sdcard:update.zip
- It will take awhile, then reboots... then it takes a really long while. Go make a sandwich.
- Once it is booted again, verify that FRF50 is installed.
- In my case, it immediately started doing a synchronization. If it does, I'd recommend waiting until it finishes.
- Delete update.zip from the microSD
- Put this file as 'update.zip' in the root of your microSD.
- Repeat steps #3 through #10.
- This time, after applying the update, you'll have to select the reboot option.
- It's going to take awhile. Go check your email.
- Once it boots up, verify that FRF83 is installed.
- Delete update.zip from the microSD.
- Enjoy
18 June 2010
Phone ID and Other Useful Tidbits
Let's start with a basic project:
groupId: org.eoti.android.phone
artifactId: PhoneIDTest
version: 1.0-SNAPSHOT
package: org.eoti.android.phone
Add this to your AndroidManifest.xml:
Add this convenience method to your PhoneIDTestActivity:
Insert this into the end of your onCreate method:
F:\work> mvn archetype:generate -DarchetypeCatalog=http://kallisti.eoti.org:8081/content/repositories/snapshots/archetype-catalog.xml2: http://kallisti.eoti.org:8081/content/repositories/snapshots/archetype-catalog.xml -> galatea-archetype (null)
groupId: org.eoti.android.phone
artifactId: PhoneIDTest
version: 1.0-SNAPSHOT
package: org.eoti.android.phone
F:\work> cd PhoneIDTest
F:\work\PhoneIDTest> mvn clean install
Add this to your AndroidManifest.xml:
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
Add this convenience method to your PhoneIDTestActivity:
private void showMessage(String msg)
{
Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
}
Insert this into the end of your onCreate method:
TelephonyManager mgr = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);Redeploy (mvn clean install) and run the app.
switch(mgr.getPhoneType())
{
case TelephonyManager.PHONE_TYPE_CDMA:
showMessage("CDMA");
break;
case TelephonyManager.PHONE_TYPE_GSM:
showMessage("GSM");
break;
case TelephonyManager.PHONE_TYPE_NONE:
showMessage("NONE");
break;
default:
showMessage("Unknown phone type");
}
showMessage("Device ID: " + mgr.getDeviceId());
showMessage("Version: " + mgr.getDeviceSoftwareVersion());
showMessage("Number: " + mgr.getLine1Number());
showMessage("Country: " + mgr.getNetworkOperatorName());
showMessage("Sim Serial#: " + mgr.getSimSerialNumber());
showMessage("Subscriber ID: " + mgr.getSubscriberId());
showMessage("Operator: " + mgr.getSimOperatorName());
11 June 2010
Accessing call logs
Start with a basic project:
groupId: org.eoti.android.calls
artifactId: CallLogs
version: 1.0-SNAPSHOT
package: org.eoti.android.calls
Add the READ_CONTACTS permission to your AndroidManifest.xml:
Add this to the end of your onCreate() in CallLogsActivity:
Redeploy (mvn clean install). If you watch the logs (adb logcat) while running the app on the emulator, you will see the column names listed -- but not any call logs... How do we fix that?
V/CallLogs( 870): Column Name: numbertype
V/CallLogs( 870): Column Name: new
V/CallLogs( 870): Column Name: duration
V/CallLogs( 870): Column Name: _id
V/CallLogs( 870): Column Name: numberlabel
V/CallLogs( 870): Column Name: name
V/CallLogs( 870): Column Name: number
V/CallLogs( 870): Column Name: type
V/CallLogs( 870): Column Name: date
The easy answer is that you can dial a number on the emulator (even though it won't connect) and then re-run your CallLogs app to see the number you just dialed listed as an OUTGOING call.
What about incoming and missed? Create another AVD and launch a secondary emulator. In the titlebar and taskbar icon name, you can see some number associated with each emulator instance (like: 5554). From one emulator, you can "dial" the number of the other. So, for instance, in emulator 5556 open the dialer and dial 5554. Dial from one to the other; answer some not others; Dial the other direction, etc.
Run your app again. In my case, the emulator seemed to not log MISSED calls.
At some point, you are bound to wonder what those nulls are in the managed query.... from the API:
A few brief examples of what you can do (per this tutorial):
uri: Who to get data from?
projection: What columns to return?
selection: SQL WHERE clause
selectionArgs: (to populate the selection clause)
sortOrder: SQL ORDER BY clause
So, for example,
F:\work> mvn archetype:generate -DarchetypeCatalog=http://kallisti.eoti.org:8081/content/repositories/snapshots/archetype-catalog.xml2: http://kallisti.eoti.org:8081/content/repositories/snapshots/archetype-catalog.xml -> galatea-archetype (null)
groupId: org.eoti.android.calls
artifactId: CallLogs
version: 1.0-SNAPSHOT
package: org.eoti.android.calls
F:\work> cd CallLogs
F:\work\CallLogs> mvn clean install
Add the READ_CONTACTS permission to your AndroidManifest.xml:
<uses-permission android:name="android.permission.READ_CONTACTS" />
Add this to the end of your onCreate() in CallLogsActivity:
Uri allCalls = Uri.parse("content://call_log/calls");
Cursor c = managedQuery(allCalls, null, null, null, null);
for(String colName : c.getColumnNames())
Log.v(TAG, "Column Name: " + colName);
if (c.moveToFirst())
{
do{
String id = c.getString(c.getColumnIndex(CallLog.Calls._ID));
String num = c.getString(c.getColumnIndex(CallLog.Calls.NUMBER));
int type = Integer.parseInt(c.getString(c.getColumnIndex(CallLog.Calls.TYPE)));
switch (type)
{
case 1: Log.v(TAG, id + ", " +num + ": INCOMING") ; break;
case 2: Log.v(TAG, id + ", " +num + ": OUTGOING") ;break;
case 3: Log.v(TAG, id + ", " +num + ": MISSED") ; break;
}
} while (c.moveToNext());
}
Redeploy (mvn clean install). If you watch the logs (adb logcat) while running the app on the emulator, you will see the column names listed -- but not any call logs... How do we fix that?
V/CallLogs( 870): Column Name: numbertype
V/CallLogs( 870): Column Name: new
V/CallLogs( 870): Column Name: duration
V/CallLogs( 870): Column Name: _id
V/CallLogs( 870): Column Name: numberlabel
V/CallLogs( 870): Column Name: name
V/CallLogs( 870): Column Name: number
V/CallLogs( 870): Column Name: type
V/CallLogs( 870): Column Name: date
The easy answer is that you can dial a number on the emulator (even though it won't connect) and then re-run your CallLogs app to see the number you just dialed listed as an OUTGOING call.
What about incoming and missed? Create another AVD and launch a secondary emulator. In the titlebar and taskbar icon name, you can see some number associated with each emulator instance (like: 5554). From one emulator, you can "dial" the number of the other. So, for instance, in emulator 5556 open the dialer and dial 5554. Dial from one to the other; answer some not others; Dial the other direction, etc.
Run your app again. In my case, the emulator seemed to not log MISSED calls.
At some point, you are bound to wonder what those nulls are in the managed query.... from the API:
public final Cursor managedQuery(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
A few brief examples of what you can do (per this tutorial):
uri: Who to get data from?
Uri allCalls = Uri.parse("content://call_log/calls");
projection: What columns to return?
String[] projection = new String[]{Calls._ID, Calls.NUMBER, Calls.TYPE};
selection: SQL WHERE clause
String selection = "Calls.NUMBER LIKE '65%'"; //---retrieve numbers beginning with 65---
String[] selectionArgs = null;
selectionArgs: (to populate the selection clause)
sortOrder: SQL ORDER BY clause
String sortOrder = "Calls.TYPE DESC"; //---sort result by call TYPE descending---
So, for example,
Cursor c = managedQuery(would return the _id, number, type columns from numbers that started with 65 in descending order....
Uri.parse("content://call_log/calls"),
new String[]{Calls._ID, Calls.NUMBER, Calls.TYPE},
"Calls.NUMBER LIKE '65%'",
null,
"Calls.TYPE DESC"
);
Subscribe to:
Posts (Atom)

