Subversion Repositories ES

Compare Revisions

Last modification

Ignore whitespace Rev 16 → Rev 17

/trunk/AndroidManifest.xml
1,49 → 1,53
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.pointedears.converter"
android:versionCode="1"
android:versionName="1.0">
package="de.pointedears.converter" android:versionCode="1"
android:versionName="1.0">
 
<uses-sdk android:minSdkVersion="8" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application android:name="ConverterApplication"
android:label="@string/app_name"
android:icon="@drawable/icon">
<uses-sdk android:minSdkVersion="8" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
<activity android:name="MenuActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<application android:name="ConverterApplication"
android:label="@string/app_name" android:icon="@drawable/icon"
android:debuggable="true">
 
<activity android:name=".app.LengthsActivity"
android:label="@string/activity_lengths">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.CONVERTER" />
</intent-filter>
</activity>
<activity android:name=".app.TemperaturesActivity"
android:label="@string/activity_temperatures">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.CONVERTER" />
</intent-filter>
</activity>
<activity android:name="MenuActivity" android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
 
<activity android:name=".app.CurrenciesActivity"
android:label="@string/activity_currencies">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.CONVERTER" />
</intent-filter>
</activity>
</application>
<activity android:name=".app.LengthsActivity"
android:label="@string/activity_lengths">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.CONVERTER" />
</intent-filter>
</activity>
 
<activity android:name=".app.TemperaturesActivity"
android:label="@string/activity_temperatures">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.CONVERTER" />
</intent-filter>
</activity>
 
<activity android:name=".app.CurrenciesActivity"
android:label="@string/activity_currencies">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.CONVERTER" />
</intent-filter>
</activity>
 
<service android:enabled="true" android:name=".helpers.UpdateService">
<intent-filter>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</service>
</application>
</manifest>
/trunk/src/de/pointedears/converter/app/CurrenciesActivity.java
1,11 → 1,17
package de.pointedears.converter.app;
 
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map.Entry;
 
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.text.Editable;
import android.view.KeyEvent;
import android.view.Menu;
23,9 → 29,10
import android.widget.TableRow;
import android.widget.TextView;
import de.pointedears.converter.R;
import de.pointedears.converter.db.ConversionData;
import de.pointedears.converter.db.CurrenciesDatabase;
import de.pointedears.converter.helpers.ConverterThread;
import de.pointedears.converter.net.RatesUpdater;
import de.pointedears.converter.helpers.Notifier;
import de.pointedears.converter.helpers.UpdateService;
 
/**
* Activity that implements currency conversion
34,6 → 41,16
*/
public class CurrenciesActivity extends Activity
{
/**
* String to use to indicate that an exchange rate has never been updated
*/
private static final String TEXT_NEVER = "never";
 
/**
* Serialization version id
*/
private static final long serialVersionUID = 1L;
 
/*
* Constants for mapping value strings
*
58,16 → 75,44
/* Unit spinners (dropdowns) */
private Spinner spinnerUnit1;
private Spinner spinnerUnit2;
private CurrenciesDatabase db;
private CurrenciesDatabase database;
 
private HashMap<String, Double> conversionRates;
private ConverterThread updateThread;
private Handler handler;
private HashMap<String, ConversionData> conversionRates;
 
private RatesUpdater updateRates;
/**
* Receiver for intent broadcasts, registered in
* {@link CurrenciesActivity#onCreate(Bundle)}
*/
public class UpdateBroadcastReceiver extends BroadcastReceiver
{
/**
* Notification message template
*/
private static final String EXCHANGE_RATES_UPDATED_TO =
" exchange rates updated to ";
 
/*
* (non-Javadoc)
*
* @see android.content.BroadcastReceiver#onReceive(android.content.Context,
* android.content.Intent)
*/
@Override
public void onReceive(Context context, Intent intent)
{
if (intent.getAction().equals(UpdateService.ACTION_UPDATE))
{
Bundle extras = intent.getExtras();
DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); //$NON-NLS-1$
Notifier.sendMessage(CurrenciesActivity.this,
extras.get(UpdateService.EXTRA_NUM_RATES)
+ UpdateBroadcastReceiver.EXCHANGE_RATES_UPDATED_TO
+ df.format(extras.get(UpdateService.EXTRA_DATE)));
}
}
}
 
/** Called when the activity is first created. */
 
@Override
public void onCreate(Bundle savedInstanceState)
{
74,9 → 119,12
super.onCreate(savedInstanceState);
this.setContentView(R.layout.activity_currencies);
 
UpdateBroadcastReceiver br = new UpdateBroadcastReceiver();
this.registerReceiver(br, new IntentFilter(UpdateService.ACTION_UPDATE));
 
/* Set up currency database, and retrieve conversion rates */
this.db = new CurrenciesDatabase(this);
this.conversionRates = this.db.getConversionRates();
this.database = new CurrenciesDatabase(this);
this.setConversionRates(this.getDatabase().getConversionRates());
this.fillTableRates();
 
final EditText editValue1 =
192,25 → 240,25
editValue2.setText("");
}
});
 
if (this.handler == null)
{
this.handler = new Handler();
}
 
this.updateThread = null;
}
 
/**
* Fills the table with currency conversion rates
*/
private void fillTableRates()
public void fillTableRates()
{
TableLayout tableRates =
(TableLayout) this.findViewById(R.id.currencies_table_rates);
 
for (Entry<String, Double> factorEntry : this.conversionRates.entrySet())
/* Remove any pre-existing currency rows */
while (tableRates.getChildCount() > 3)
{
tableRates.removeViewAt(3);
}
 
for (Entry<String, ConversionData> factorEntry : this.getConversionRates()
.entrySet())
{
TableRow row = new TableRow(this);
 
TextView columnCurrency1 = new TextView(this);
218,9 → 266,25
row.addView(columnCurrency1);
 
TextView columnRate = new TextView(this);
columnRate.setText(factorEntry.getValue().toString());
final ConversionData conversionData = factorEntry.getValue();
columnRate.setText(conversionData.getRate().toString());
row.addView(columnRate);
 
TextView columnUpdated = new TextView(this);
Date updated = conversionData.getUpdated();
if (updated.getTime() > 0)
{
DateFormat df =
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM);
columnUpdated.setText(df.format(updated));
}
else
{
columnUpdated.setText(CurrenciesActivity.TEXT_NEVER);
}
 
row.addView(columnUpdated);
 
tableRates.addView(row);
}
}
251,28 → 315,28
* NOTE: Had to do it the complicated way because somehow the Android SDK
* won't get it another way
*/
Double factorToEuro = null;
ConversionData conversionData1 = null;
Double factorToEuro = 1.0;
if (selectedItemValue1 != null)
{
factorToEuro = this.conversionRates.get(selectedItemValue1);
conversionData1 = this.getConversionRates().get(selectedItemValue1);
if (conversionData1 != null)
{
factorToEuro = conversionData1.getRate();
}
}
 
if (factorToEuro == null)
{
factorToEuro = 1.0;
}
 
Double factorFromEuro = null;
ConversionData conversionData2 = null;
Double factorFromEuro = 1.0;
if (selectedItemValue2 != null)
{
factorFromEuro = this.conversionRates.get(selectedItemValue2);
conversionData2 = this.getConversionRates().get(selectedItemValue2);
if (conversionData2 != null)
{
factorFromEuro = conversionData2.getRate();
}
}
 
if (factorFromEuro == null)
{
factorFromEuro = 1.0;
}
 
newValue = newValue / factorToEuro * factorFromEuro;
 
return newValue.toString();
304,47 → 368,24
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
// Handle item selection
/* Handle item selection */
switch (item.getItemId())
{
case R.id.item_options_update:
if (this.updateThread == null)
{
this.updateRates = new RatesUpdater(this);
this.updateThread =
new ConverterThread(this.updateRates, this.handler);
this.updateRates.setUpdateThread(this.updateThread);
}
/*
* Request the update service to run a thread to fetch the rates from
* the Web (project requirement)
*/
Intent intent = new Intent(this, UpdateService.class);
intent.setAction(UpdateService.ACTION_UPDATE);
 
try
{
this.updateThread.start();
// this.editValue1.setText("Gestartet!");
}
catch (IllegalThreadStateException e)
{
// this.editValue1.setText("Bereits gestartet!");
}
return true;
/*
* FIXME: Not thread-safe!
* Get the activity context from the intent directly instead
*/
UpdateService.setActivityContext(this);
 
case R.id.item_options_quit:
if (this.updateThread != null)
{
try
{
this.updateThread.join();
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
}
 
// this.editValue1.setText("Gestoppt -> Warten auf Start");
}
else
{
// this.editValue1.setText("Bereits gestoppt -> Warten auf Start");
}
this.startService(intent);
return true;
 
default:
351,4 → 392,29
return super.onOptionsItemSelected(item);
}
}
 
/**
* @return the conversionRates
*/
public HashMap<String, ConversionData> getConversionRates()
{
return this.conversionRates;
}
 
/**
* @param conversionRates
* the conversionRates to set
*/
public void setConversionRates(HashMap<String, ConversionData> conversionRates)
{
this.conversionRates = conversionRates;
}
 
/**
* @return the database
*/
public CurrenciesDatabase getDatabase()
{
return this.database;
}
}
/trunk/src/de/pointedears/converter/MenuActivity.java
32,6 → 32,7
import android.view.View;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import de.pointedears.converter.helpers.UpdateService;
 
/**
* Generates the main menu as a list of activities from the manifest
43,12 → 44,13
*/
public class MenuActivity extends ListActivity
{
 
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
 
this.startService(new Intent(this, UpdateService.class));
 
Intent intent = this.getIntent();
String path = intent.getStringExtra("de.pointedears.converter.Path"); //$NON-NLS-1$
 
/trunk/src/de/pointedears/converter/helpers/Notifier.java
0,0 → 1,96
/**
* A simple notifier for Activities, encapsulating all the work necessary
* to send a notification message on Android.
*/
package de.pointedears.converter.helpers;
 
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import de.pointedears.converter.R;
import de.pointedears.converter.R.drawable;
 
/**
* Sends notification messages for {@link Activity Activities}
*
* @author Thomas 'PointedEars' Lahn
*/
public class Notifier
{
private static int nextId = 1;
 
/**
*
*/
public Context activityContext;
 
/**
* @param activityContext
* The activity for which a notification will be sent. This
* allows the user to select the notification and go back
* directly to the activity that issued it.
* @param tickerText
* The text to be displayed as notification, both as ticker message
* and in the notification list.
* @param notificationTitle
* The title for the notification message. The default (
* <code>null</code>)
* is the application name (R.strings.app_name).
* @see Notifier#sendMessage(Context, CharSequence)
*/
public static void sendMessage(Context activityContext,
CharSequence tickerText, CharSequence notificationTitle)
{
NotificationManager mNotificationManager =
(NotificationManager) activityContext
.getSystemService(Context.NOTIFICATION_SERVICE);
 
Notification notification =
new Notification(drawable.icon, tickerText, System.currentTimeMillis());
 
Context applicationContext =
activityContext.getApplicationContext();
 
Intent notificationIntent =
new Intent(activityContext, activityContext.getClass());
 
PendingIntent contentIntent =
PendingIntent.getActivity(activityContext, 0,
notificationIntent, 0);
 
if (notificationTitle == null)
{
notificationTitle = activityContext.getString(R.string.app_name);
}
 
CharSequence contentTitle = notificationTitle;
CharSequence contentText = tickerText;
 
notification.setLatestEventInfo(applicationContext, contentTitle,
contentText,
contentIntent);
 
mNotificationManager.notify(Notifier.nextId, notification);
++Notifier.nextId;
}
 
/**
* Sends a notification message where the the application name
* (R.strings.app_name)
* is used as message title.
*
* @param activityContext
* The activity for which a notification will be sent
* @param tickerText
* The text to be displayed as notification
* @see Notifier#sendMessage(Context, CharSequence, CharSequence)
*/
public static void sendMessage(Context activityContext,
CharSequence tickerText)
{
Notifier.sendMessage(activityContext, tickerText, null);
}
}
Property changes:
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Index: src/de/pointedears/converter/helpers/UpdateService.java
===================================================================
--- src/de/pointedears/converter/helpers/UpdateService.java (nonexistent)
+++ src/de/pointedears/converter/helpers/UpdateService.java (revision 17)
@@ -0,0 +1,157 @@
+package de.pointedears.converter.helpers;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import de.pointedears.converter.app.CurrenciesActivity;
+import de.pointedears.converter.net.RatesUpdater;
+
+/**
+ * Update service to run a thread to update currency rates
+ *
+ * @author Thomas 'PointedEars' Lahn
+ */
+public class UpdateService extends Service
+{
+ // private Timer myTimer = null;
+ // private final BroadcastTimerTask sendTime = null;
+ private static CurrenciesActivity activityContext;
+ private ConverterThread updateThread;
+ private Handler handler;
+ private RatesUpdater ratesUpdater;
+
+ public static final String ACTION_UPDATE =
+ "de.pointedears.converter.ACTION_UPDATE"; //$NON-NLS-1$
+ public static final String EXTRA_ACTIVITY =
+ "de.pointedears.converter.extra_activity"; //$NON-NLS-1$
+ public static final String EXTRA_NUM_RATES =
+ "de.pointedears.converter.extra_num_rates"; //$NON-NLS-1$
+ public static final String EXTRA_DATE = "de.pointedears.converter.extra_date"; //$NON-NLS-1$
+
+ // private sendTimerTask sendTime = null;
+
+ // /** inner class implements the broadcast timer */
+ // private class BroadcastTimerTask extends TimerTask
+ // {
+ // int counter = 0;
+ //
+ // @Override
+ // public void run()
+ // {
+ // Intent intent = new Intent(UpdateService.ACTION_UPDATE);
+ // String theTime =
+ // "Time: " + System.currentTimeMillis() + ", Counter = "
+ // + Integer.toString(this.counter);
+ // this.counter++;
+ // intent.putExtra("TIME", theTime);
+ // UpdateService.this.sendBroadcast(intent);
+ // }
+ // };
+
+ @Override
+ public IBinder onBind(Intent intent)
+ {
+ /* NOTE: Clients cannot bind to this service */
+ return null;
+ }
+
+ @Override
+ public void onCreate()
+ {
+ super.onCreate();
+ // this.myTimer = new Timer("myTimer");
+
+ if (this.handler == null)
+ {
+ this.handler = new Handler();
+ }
+
+ this.updateThread = null;
+ }
+
+ // /*
+ // * (non-Javadoc)
+ // *
+ // * @see android.app.Service#onStartCommand(android.content.Intent, int, int)
+ // */
+ // @Override
+ // public int onStartCommand(Intent intent, int flags, int startId)
+ // {
+ // // TODO Auto-generated method stub
+ // return super.onStartCommand(intent, flags, startId);
+ // }
+
+ @Override
+ /**
+ * @deprecated since SDK 2.0
+ */
+ public void onStart(Intent intent, int startId)
+ {
+ super.onStart(intent, startId);
+ // this.myTimer.cancel();
+ // this.myTimer = new Timer("myTimer");
+ // this.sendTime = new BroadcastTimerTask();
+ // this.myTimer.scheduleAtFixedRate(this.sendTime, 0, 1000 * 5);
+
+ String action = intent.getAction();
+ if (UpdateService.ACTION_UPDATE.equals(action))
+ {
+ if (this.updateThread == null)
+ {
+ this.ratesUpdater =
+ new RatesUpdater(UpdateService.getActivityContext(), this);
+ this.updateThread =
+ new ConverterThread(this.ratesUpdater, this.handler);
+ this.ratesUpdater.setUpdateThread(this.updateThread);
+ }
+
+ try
+ {
+ this.updateThread.start();
+ // this.editValue1.setText("Gestartet!");
+ }
+ catch (IllegalThreadStateException e)
+ {
+ // this.editValue1.setText("Bereits gestartet!");
+ }
+
+ // case R.id.item_options_quit:
+ // if (this.updateThread != null)
+ // {
+ // try
+ // {
+ // this.updateThread.join();
+ // }
+ // catch (InterruptedException e)
+ // {
+ // // TODO Auto-generated catch block
+ // }
+ //
+ // // this.editValue1.setText("Gestoppt -> Warten auf Start");
+ // }
+ // else
+ // {
+ // // this.editValue1.setText("Bereits gestoppt -> Warten auf Start");
+ // }
+ // return true;
+ }
+ }
+
+ /**
+ * @return the activityContext
+ */
+ public static CurrenciesActivity getActivityContext()
+ {
+ return UpdateService.activityContext;
+ }
+
+ /**
+ * @param activityContext
+ * the activityContext to set
+ */
+ public static void setActivityContext(CurrenciesActivity activityContext)
+ {
+ UpdateService.activityContext = activityContext;
+ }
+}
/src/de/pointedears/converter/helpers/UpdateService.java
Property changes:
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Index: src/de/pointedears/converter/db/ConversionData.java
===================================================================
--- src/de/pointedears/converter/db/ConversionData.java (nonexistent)
+++ src/de/pointedears/converter/db/ConversionData.java (revision 17)
@@ -0,0 +1,60 @@
+package de.pointedears.converter.db;
+
+import java.util.Date;
+
+/**
+ * Stores conversion data for a currency
+ *
+ * @author pelinux
+ */
+public class ConversionData
+{
+ private double rate;
+ private Date updated;
+
+ /**
+ * @param rate
+ * 1 EUR equals this value in a currency
+ * @param updated
+ * Date that the rate was updated
+ */
+ public ConversionData(Double rate, Date updated)
+ {
+ this.setRate(rate);
+ this.setUpdated(updated);
+ }
+
+ /**
+ * @return the rate
+ */
+ public Double getRate()
+ {
+ return this.rate;
+ }
+
+ /**
+ * @param rate
+ * the rate to set
+ */
+ public void setRate(Double rate)
+ {
+ this.rate = rate;
+ }
+
+ /**
+ * @return the updated
+ */
+ public Date getUpdated()
+ {
+ return this.updated;
+ }
+
+ /**
+ * @param updated
+ * the updated to set
+ */
+ public void setUpdated(Date updated)
+ {
+ this.updated = updated;
+ }
+}
\ No newline at end of file
/src/de/pointedears/converter/db/ConversionData.java
Property changes:
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Index: src/de/pointedears/converter/db/CurrenciesDatabase.java
===================================================================
--- src/de/pointedears/converter/db/CurrenciesDatabase.java (revision 16)
+++ src/de/pointedears/converter/db/CurrenciesDatabase.java (revision 17)
@@ -3,14 +3,20 @@
*/
package de.pointedears.converter.db;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
import java.util.HashMap;
import java.util.Map.Entry;
import android.content.ContentValues;
+import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
import de.pointedears.converter.app.CurrenciesActivity;
/**
@@ -19,29 +25,38 @@
*/
public class CurrenciesDatabase extends SQLiteOpenHelper
{
+
private static final String DATABASE_NAME = "currency.db"; //$NON-NLS-1$
- private static final int DATABASE_VERSION = 3;
+ private static final int DATABASE_VERSION = 8;
private static final String TABLE = "currency"; //$NON-NLS-1$
private static final String COLUMN_CURRENCY = "currency1"; //$NON-NLS-1$
private static final String COLUMN_FACTOR = "factor"; //$NON-NLS-1$
+ private static final String COLUMN_UPDATED = "updated"; //$NON-NLS-1$
- private static HashMap<String, Double> conversionRates =
- new HashMap<String, Double>();
+ private static HashMap<String, ConversionData> conversionRates =
+ new HashMap<String, ConversionData>();
static
{
/* Default conversion rates from Euro (EUR) to other currencies */
+ Date epoch = new Date(0);
CurrenciesDatabase.conversionRates
- .put(CurrenciesActivity.VALUE_CHF, 1.3013);
+ .put(CurrenciesActivity.VALUE_CHF,
+ new ConversionData(1.3013, epoch));
CurrenciesDatabase.conversionRates
- .put(CurrenciesActivity.VALUE_USD, 1.3521);
+ .put(CurrenciesActivity.VALUE_USD,
+ new ConversionData(1.3521, epoch));
}
+ private SQLiteDatabase database;
+ private final DateFormat iso8601format = new SimpleDateFormat(
+ "yyyy-MM-dd HH:mm:ss");
+
/**
* @param context
* The Activity in which this wrapper is used
*/
- public CurrenciesDatabase(CurrenciesActivity context)
+ public CurrenciesDatabase(Context context)
{
super(context, CurrenciesDatabase.DATABASE_NAME, null,
CurrenciesDatabase.DATABASE_VERSION);
@@ -60,22 +75,62 @@
public void onCreate(SQLiteDatabase db)
{
db.execSQL("CREATE TABLE IF NOT EXISTS " + CurrenciesDatabase.TABLE
- + " (" + CurrenciesDatabase.COLUMN_CURRENCY + " TEXT, "
- + CurrenciesDatabase.COLUMN_FACTOR
- + " NUMERIC"
+ + " (" + CurrenciesDatabase.COLUMN_CURRENCY + " TEXT"
+ + ", " + CurrenciesDatabase.COLUMN_FACTOR + " NUMERIC"
+ + ", " + CurrenciesDatabase.COLUMN_UPDATED
+ + " TEXT"
+ ", CONSTRAINT unique_currency_pair UNIQUE ("
- + CurrenciesDatabase.COLUMN_CURRENCY + "))");
+ + CurrenciesDatabase.COLUMN_CURRENCY + ") ON CONFLICT REPLACE)");
- HashMap<String, Double> currencyConversions =
+ this.writeConversionsToDatabase(db);
+ }
+
+ /**
+ * @param db
+ * The database; <code>null</code> uses the default database
+ */
+ public void writeConversionsToDatabase(SQLiteDatabase db)
+ {
+ HashMap<String, ConversionData> currencyConversions =
this.getConversionRates();
- for (Entry<String, Double> factorEntry : currencyConversions.entrySet())
+
+ if (db == null)
{
- ContentValues values = new ContentValues();
- values.put(CurrenciesDatabase.COLUMN_CURRENCY, factorEntry.getKey());
- values.put(CurrenciesDatabase.COLUMN_FACTOR, factorEntry.getValue());
- db.insert(CurrenciesDatabase.TABLE, CurrenciesDatabase.COLUMN_FACTOR,
- values);
+ db = this.database;
}
+
+ if (!db.isOpen())
+ {
+ try
+ {
+ db = this.getWritableDatabase();
+ }
+ catch (SQLiteException e)
+ {
+ Log.e(this.getClass().toString(), "Could not open database", e);
+ throw e;
+ }
+ }
+
+ if (db.isOpen())
+ {
+ for (Entry<String, ConversionData> factorEntry : currencyConversions
+ .entrySet())
+ {
+ ContentValues values = new ContentValues();
+ values.put(CurrenciesDatabase.COLUMN_CURRENCY, factorEntry.getKey());
+ values.put(CurrenciesDatabase.COLUMN_FACTOR, factorEntry.getValue()
+ .getRate());
+ values.put(CurrenciesDatabase.COLUMN_UPDATED,
+ this.iso8601format.format(factorEntry.getValue()
+ .getUpdated()));
+
+ /* INSERT suffices here, thanks to ON CONFLICT REPLACE */
+ db.insert(CurrenciesDatabase.TABLE,
+ CurrenciesDatabase.COLUMN_FACTOR,
+ values);
+ }
+ }
}
/*
@@ -97,7 +152,7 @@
/**
* @return
*/
- public HashMap<String, Double> getConversionRates()
+ public HashMap<String, ConversionData> getConversionRates()
{
return CurrenciesDatabase.conversionRates;
}
@@ -111,11 +166,11 @@
try
{
/* Get database connection, but upgrade database first if necessary! */
- SQLiteDatabase dbConn = this.getWritableDatabase();
+ this.database = this.getWritableDatabase();
Cursor cursor =
- dbConn.query(true, CurrenciesDatabase.TABLE, null, null, null, null,
- null, CurrenciesDatabase.COLUMN_CURRENCY, null);
+ this.database.query(true, CurrenciesDatabase.TABLE, null, null, null,
+ null, null, CurrenciesDatabase.COLUMN_CURRENCY, null);
if (cursor != null)
{
@@ -126,18 +181,37 @@
.getColumnIndexOrThrow(CurrenciesDatabase.COLUMN_CURRENCY);
int factorId =
cursor.getColumnIndexOrThrow(CurrenciesDatabase.COLUMN_FACTOR);
+ int updatedId =
+ cursor.getColumnIndexOrThrow(CurrenciesDatabase.COLUMN_UPDATED);
/* NOTE: Don't change the default values if the table is empty */
if (cursor.moveToFirst())
{
- HashMap<String, Double> newCurrencyConversions =
- new HashMap<String, Double>();
+ HashMap<String, ConversionData> newCurrencyConversions =
+ new HashMap<String, ConversionData>();
do
{
String currencyStr = cursor.getString(currency1Id);
Double factor = cursor.getDouble(factorId);
- newCurrencyConversions.put(currencyStr, factor);
+ String updatedStr = cursor.getString(updatedId);
+
+ Date updated = new Date(0);
+ try
+ {
+ if (updatedStr != null)
+ {
+ updated = this.iso8601format.parse(updatedStr);
+ }
+ }
+ catch (ParseException e)
+ {
+ Log.e(this.getClass().toString(),
+ "Parsing ISO8601 datetime failed: '" + updatedStr + "'", e);
+ }
+
+ newCurrencyConversions.put(currencyStr, new ConversionData(
+ factor, updated));
}
while (cursor.moveToNext());
@@ -146,72 +220,16 @@
}
catch (IllegalArgumentException e)
{
- /* Could not retrieve column index */
- e.printStackTrace();
+ Log.e(this.getClass().toString(), "Could not retrieve column index",
+ e);
}
}
- dbConn.close();
+ this.database.close();
}
catch (SQLiteException e1)
{
- /* Could not open database */
- e1.printStackTrace();
+ Log.e(this.getClass().toString(), "Could not open database", e1);
}
}
-
- /**
- * Tests the database access
- */
- public void testAccess()
- {
- try
- {
- SQLiteDatabase dbConn = this.getReadableDatabase();
-
- Cursor myCursor =
- dbConn.query(true, CurrenciesDatabase.TABLE, null, null, null, null,
- null, CurrenciesDatabase.COLUMN_CURRENCY, null);
-
- @SuppressWarnings({ "unused", "nls" })
- String queryResult = "";
- if (myCursor != null)
- {
- try
- {
- int currency1Id =
- myCursor
- .getColumnIndexOrThrow(CurrenciesDatabase.COLUMN_CURRENCY);
- int factorId =
- myCursor.getColumnIndexOrThrow(CurrenciesDatabase.COLUMN_FACTOR);
-
- if (myCursor.moveToFirst())
- {
- do
- {
- String currencyStr = myCursor.getString(currency1Id);
- Double factor = myCursor.getDouble(factorId);
-
- /* DEBUG */
- queryResult +=
- "EUR --> " + currencyStr + ": " + factor + "\n"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
- }
- while (myCursor.moveToNext());
- }
- }
- catch (IllegalArgumentException e)
- {
- /* Could not retrieve column index */
- e.printStackTrace();
- }
- }
-
- dbConn.close();
- }
- catch (SQLiteException e1)
- {
- /* Could not open database */
- e1.printStackTrace();
- }
- }
}
/trunk/src/de/pointedears/converter/net/RatesUpdater.java
4,6 → 4,11
package de.pointedears.converter.net;
 
import java.io.IOException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
 
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
15,18 → 20,19
import javax.xml.xpath.XPathFactory;
 
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
 
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import de.pointedears.converter.R;
import de.pointedears.converter.app.CurrenciesActivity;
import de.pointedears.converter.db.ConversionData;
import de.pointedears.converter.helpers.ConverterThread;
import de.pointedears.converter.helpers.UpdateService;
 
/**
* @author pelinux
37,15 → 43,20
private static final String URL_ECB =
"http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml"; //$NON-NLS-1$
 
private final Context activityContext;
private final CurrenciesActivity activityContext;
private ConverterThread updateThread = null;
 
private final UpdateService service;
 
/**
* @param updateService
*
*/
public RatesUpdater(Context activityContext)
public RatesUpdater(CurrenciesActivity activityContext,
UpdateService updateService)
{
this.activityContext = activityContext;
this.service = updateService;
}
 
/**
72,20 → 83,28
@Override
public void run()
{
int len = 0;
DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); //$NON-NLS-1$
Date updated = new Date();
 
if (this.getUpdateThread() != null)
{
// CurrenciesActivity.this.editValue1.setText("42");
TextView textUpdating =
(TextView) this.activityContext
.findViewById(R.id.currencies_text_updating);
textUpdating.setVisibility(View.VISIBLE);
 
DocumentBuilderFactory documentBuilderFactory =
DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
DocumentBuilder builder;
try
{
DocumentBuilder builder =
documentBuilderFactory.newDocumentBuilder();
builder = documentBuilderFactory.newDocumentBuilder();
Document doc;
try
{
Document doc = builder.parse(RatesUpdater.URL_ECB);
doc = builder.parse(RatesUpdater.URL_ECB);
XPathFactory xpathFactory = XPathFactory.newInstance();
XPath xpath = xpathFactory.newXPath();
// NamespaceContextHelper namespaceContext =
100,80 → 119,95
* FIXME: Why doesn't a simple "./Cube/Cube/Cube" work even with a
* namespace resolver?
*/
@SuppressWarnings("nls")
XPathExpression expr =
xpath
.compile("./*[local-name() = 'Cube']/*[local-name() = 'Cube']/*[local-name() = 'Cube']"); //$NON-NLS-1$
Object result =
.compile("./*[local-name() = 'Cube']/*[local-name() = 'Cube']");
NodeList nodes = (NodeList)
expr.evaluate(doc.getDocumentElement(), XPathConstants.NODESET);
NodeList nodes = (NodeList) result;
Element parentCube = (Element) nodes.item(0);
if (parentCube == null)
{
return;
}
 
int len = nodes.getLength();
try
{
updated = df.parse(parentCube.getAttribute("time"));
}
catch (ParseException e)
{
Log.e(this.getClass().toString(),
"Could not parse the `time' attribute into a Date", e);
}
 
String ns = Context.NOTIFICATION_SERVICE;
NotificationManager mNotificationManager =
(NotificationManager) this.activityContext
.getSystemService(ns);
expr =
xpath
.compile("./*[local-name()='Cube' and (@currency='CHF' or @currency='USD')]"); //$NON-NLS-1$
nodes =
(NodeList) expr.evaluate(parentCube, XPathConstants.NODESET);
NodeList childCubes = nodes;
 
int icon = R.drawable.icon;
CharSequence tickerText = "Found " + len + " nodes!";
long when = System.currentTimeMillis();
len = childCubes.getLength();
 
Notification notification =
new Notification(icon, tickerText, when);
 
Context applicationContext =
this.activityContext.getApplicationContext();
CharSequence contentTitle = "Converter";
CharSequence contentText = "Found " + len + " nodes!";
Intent notificationIntent =
new Intent(this.activityContext, this.activityContext.getClass());
PendingIntent contentIntent =
PendingIntent.getActivity(this.activityContext, 0,
notificationIntent, 0);
 
notification.setLatestEventInfo(applicationContext, contentTitle,
contentText,
contentIntent);
 
// private static final int HELLO_ID = 1;
 
mNotificationManager.notify(1, notification);
 
HashMap<String, ConversionData> conversionRates =
this.activityContext.getConversionRates();
for (int i = 0; i < len; ++i)
{
Node item = nodes.item(i);
NamedNodeMap attributes = item.getAttributes();
String currency =
attributes
.getNamedItem("currency").getNodeValue(); //$NON-NLS-1$
String rate = attributes.getNamedItem("rate").getNodeValue(); //$NON-NLS-1$
Element item = (Element) childCubes.item(i);
String currency = item.getAttribute("currency");
 
/* TODO: Update UI */
System.out.println(currency + ": " + rate); //$NON-NLS-1$
try
{
Double rate =
Double.parseDouble(item.getAttribute("rate")); //$NON-NLS-1$
conversionRates
.put(currency, new ConversionData(rate, updated));
}
catch (NumberFormatException e)
{
 
}
}
 
this.activityContext.getDatabase().writeConversionsToDatabase(null);
this.activityContext.fillTableRates();
}
catch (XPathExpressionException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
Log.e(this.getClass().toString(), "Error in XPath expression", e);
}
}
catch (SAXException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
Log.e(this.getClass().toString(),
"Exception while parsing external XML resource", e);
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
Log.e(this.getClass().toString(),
"I/O exception while parsing external XML resource", e);
}
}
catch (ParserConfigurationException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
Log.e(this.getClass().toString(),
"Document builder cannot be created", e);
}
 
if (len > 0)
{
/*
* Notify the activity that we are done (causes a notification to be
* shown)
*/
Intent intent = new Intent(UpdateService.ACTION_UPDATE);
intent.putExtra(UpdateService.EXTRA_NUM_RATES, len);
intent.putExtra(UpdateService.EXTRA_DATE, updated);
this.service.sendBroadcast(intent);
}
 
textUpdating.setVisibility(View.GONE);
}
}
}
/trunk/bin/Converter.apk
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/bin/resources.ap_
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/bin/classes.dex
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/res/values/strings.xml
10,8 → 10,10
<string name="temperatures_off_scale"><sup>*</sup> Theoretical value off the scale</string>
 
<string name="activity_currencies">Currencies</string>
<string name="currencies_updating">Updating table rates ...</string>
<string name="currencies_currency"><b>Currency</b></string>
<string name="currencies_rate"><b>Rate (1 EUR = ?)</b></string>
<string name="currencies_rate"><b>Rate (1 EUR)</b></string>
<string name="currencies_updated"><b>Updated</b></string>
<string name="caption_update">Update table rates</string>
<string name="option_quit">Quit</string>
</resources>
/trunk/res/menu/options.xml
6,6 → 6,6
android:title="@string/caption_update" />
 
<!-- android:icon="@drawable/ic_quit" -->
<item android:id="@+id/item_options_quit"
android:title="@string/option_quit" />
<!-- <item android:id="@+id/item_options_quit"
android:title="@string/option_quit" /> -->
</menu>
/trunk/res/layout/activity_lengths.xml
1,10 → 1,10
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_height="fill_parent"
android:layout_width="fill_parent">
android:orientation="vertical" android:layout_height="match_parent"
android:layout_width="match_parent">
 
<TableLayout android:id="@+id/TableLayout01"
android:layout_height="wrap_content" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_width="match_parent"
android:stretchColumns="1,3" android:shrinkColumns="*"
android:layout_marginTop="10sp">
<TableRow android:layout_height="wrap_content">
/trunk/res/layout/activity_temperatures.xml
1,10 → 1,10
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_height="fill_parent"
android:layout_width="fill_parent">
android:orientation="vertical" android:layout_height="match_parent"
android:layout_width="match_parent">
 
<TableLayout android:id="@+id/TableLayout01"
android:layout_height="wrap_content" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_width="match_parent"
android:stretchColumns="1,3" android:shrinkColumns="*"
android:layout_marginTop="10sp">
<TableRow android:layout_height="wrap_content">
28,8 → 28,9
</TableRow>
</TableLayout>
 
<TextView android:text="@string/temperatures_off_scale"
android:id="@+id/temperatures_text_off_scale" android:layout_width="wrap_content"
<TextView android:id="@+id/temperatures_text_off_scale"
android:text="@string/temperatures_off_scale"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:visibility="invisible" />
<Button android:text="@string/button_clear" android:id="@+id/temperatures_button_clear"
android:layout_height="wrap_content" android:layout_width="100sp"
/trunk/res/layout/activity_currencies.xml
1,10 → 1,10
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_height="fill_parent"
android:layout_width="fill_parent">
android:orientation="vertical" android:layout_height="match_parent"
android:layout_width="match_parent">
 
<TableLayout android:id="@+id/currencies_table_widget"
android:layout_height="wrap_content" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_width="match_parent"
android:stretchColumns="1,3" android:shrinkColumns="*"
android:layout_marginTop="10sp">
<TableRow android:layout_height="wrap_content">
31,15 → 31,23
<Button android:text="@string/button_clear" android:id="@+id/currencies_button_clear"
android:layout_height="wrap_content" android:layout_width="100sp"
android:layout_gravity="center_horizontal"></Button>
 
<TableLayout android:id="@+id/currencies_table_rates"
android:scrollbars="vertical" android:layout_height="wrap_content"
android:layout_width="fill_parent" android:stretchColumns="*">
<TextView android:id="@+id/currencies_text_updating"
android:text="@string/currencies_updating"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:visibility="gone" />
<TableLayout android:id="@+id/currencies_table_rates"
android:layout_height="match_parent"
android:layout_width="match_parent" android:stretchColumns="*"
>
<View android:layout_height="2dip" android:background="#FF909090" />
<TableRow>
<TextView android:text="@string/currencies_currency" />
<TextView android:text="@string/currencies_rate" />
<TextView android:text="@string/currencies_updated" />
</TableRow>
<View android:layout_height="1dip" android:background="#FF909090" />
</TableLayout>
</TableLayout>
</LinearLayout>