- Currencies: + Added/updated resource strings + Disabled "Quit" (options menu button) - Added update notifications - Added exchange rates update service (project requirement) - Clean-up: + Outsourced conversion data + Layout: Replaced deprecated fill_parent with match_parent
/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> |