diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 1d430a3..5b00676 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,7 +1,7 @@ + package="com.eightbitcloud.internode" android:versionName="2.13" + android:versionCode="19"> @@ -24,9 +24,31 @@ + diff --git a/Entrust_net.cer b/Entrust_net.cer new file mode 100644 index 0000000..ce27297 Binary files /dev/null and b/Entrust_net.cer differ diff --git a/Notes.txt b/Notes.txt new file mode 100644 index 0000000..4b12a4d --- /dev/null +++ b/Notes.txt @@ -0,0 +1,87 @@ + /web/oscportal.portal?_nfpb=true&portlet_viewmyusage_2_actionOverride=%2Fportlets%2Fosc%2FViewMyUsage%2FgoToEBPPUrl&_windowLabel=portlet_viewmyusage_2&portlet_viewmyusage_2accNum=obYJVkQwXEmXbLFkddgTuw%3D%3D&portlet_viewmyusage_2pageName=unbilledUsage&portlet_viewmyusage_2virAcctNum=Dqwz4w3Slk3i%2BrzFSOOB%2FA%3D%3D&_pageLabel=myusage_postpaid +0418129969 + +A guy has send me a link to optus wireless broadband stuff + +Broadband Usage +SMS Usage + +It comes from a text blob that looks like: + +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: HTTP15: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: 0422119653 +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: + +16:30:58.076 28123 28158 D NodeDroid: HTTP15: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: +11-09 16:30:58.076 28123 28158 D NodeDroid: + + \ No newline at end of file diff --git a/createKeystore.sh b/createKeystore.sh index a6cbd78..514215f 100755 --- a/createKeystore.sh +++ b/createKeystore.sh @@ -1,2 +1,3 @@ -#!/binsh +#!/bin/sh keytool -keystore assets/my.bks -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider -storepass changeit -importcert -trustcacerts -alias GeoTrust -file GeoTrust_Global_CA.cer +keytool -keystore assets/my.bks -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider -storepass changeit -importcert -trustcacerts -alias GeoTrust -file Entrust_net.cer diff --git a/lib/dom4j-1.6.1.jar b/lib/dom4j-1.6.1.jar deleted file mode 100644 index c8c4dbb..0000000 Binary files a/lib/dom4j-1.6.1.jar and /dev/null differ diff --git a/lib/jtidy-r938.jar b/lib/jtidy-r938.jar deleted file mode 100644 index efde902..0000000 Binary files a/lib/jtidy-r938.jar and /dev/null differ diff --git a/res/drawable/ic_menu_directions.png b/res/drawable/ic_menu_directions.png new file mode 100644 index 0000000..67d3ff2 Binary files /dev/null and b/res/drawable/ic_menu_directions.png differ diff --git a/res/drawable/padded_background.xml b/res/drawable/padded_background.xml new file mode 100644 index 0000000..f251462 --- /dev/null +++ b/res/drawable/padded_background.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/res/drawable/pending.png b/res/drawable/pending.png new file mode 100644 index 0000000..cebfb97 Binary files /dev/null and b/res/drawable/pending.png differ diff --git a/res/drawable/widget_background.xml b/res/drawable/widget_background.xml new file mode 100644 index 0000000..4dce7cd --- /dev/null +++ b/res/drawable/widget_background.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/res/layout/browserscraper.xml b/res/layout/browserscraper.xml new file mode 100644 index 0000000..8894940 --- /dev/null +++ b/res/layout/browserscraper.xml @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/res/menu/mainmenu.xml b/res/menu/mainmenu.xml index 3c490c5..dc083c1 100644 --- a/res/menu/mainmenu.xml +++ b/res/menu/mainmenu.xml @@ -4,4 +4,5 @@ + diff --git a/res/values/arrays.xml b/res/values/arrays.xml deleted file mode 100644 index 3d225b5..0000000 --- a/res/values/arrays.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - Internode - Optus Mobile - Vodafone MBB - - - diff --git a/res/values/strings.xml b/res/values/strings.xml index d43802c..7b5c572 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -49,6 +49,8 @@ View NodeDroid support homepage Send logs to 8-bit cloud Email logs of network traffic and errors to 8-bit cloud for diagnosis +Browser Scraper +Use an embedded browser to teach NodeDroid what your usage looks like diff --git a/res/xml/usage_appwidget_info.xml b/res/xml/usage_appwidget_info.xml new file mode 100644 index 0000000..566665b --- /dev/null +++ b/res/xml/usage_appwidget_info.xml @@ -0,0 +1,10 @@ + + + + diff --git a/src/com/eightbitcloud/internode/AccountListActivity.java b/src/com/eightbitcloud/internode/AccountListActivity.java index bfefe2f..94055d4 100644 --- a/src/com/eightbitcloud/internode/AccountListActivity.java +++ b/src/com/eightbitcloud/internode/AccountListActivity.java @@ -32,6 +32,7 @@ import com.eightbitcloud.internode.data.Provider; import com.eightbitcloud.internode.data.ProviderStore; import com.eightbitcloud.internode.data.Service; +import com.eightbitcloud.internode.provider.ProviderFetcher; import com.eightbitcloud.internode.provider.WrongPasswordException; public class AccountListActivity extends ListActivity { @@ -118,7 +119,7 @@ private SharedPreferences getAccountPreferences() { public void onStart() { super.onStart(); PreferencesSerialiser.deserialise(getAccountPreferences(), accounts); - Log.d(NodeUsage.TAG, "Starting accountlist, accts is " + accounts + ", prefs = " + getAccountPreferences().getAll()); + Log.d(NodeUsage.TAG, "Starting accountlist, accts is " + accounts); ((BaseAdapter)getListAdapter()).notifyDataSetChanged(); if (accounts.isEmpty()) { createNewAccount(); @@ -142,7 +143,7 @@ public void onPause() { super.onPause(); PreferencesSerialiser.serialise(accounts, getAccountPreferences()); - Log.d(NodeUsage.TAG, "Pausing accountlist, accts is " + accounts + ", prefs = " + getAccountPreferences().getAll()); + Log.d(NodeUsage.TAG, "Pausing accountlist, accts is " + accounts); } @Override @@ -203,7 +204,7 @@ public Dialog onCreateDialog(int id) { dialog.getWindow().setLayout(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); final Spinner providerSpinner= (Spinner) dialog.findViewById(R.id.providerSpinner); - providerSpinnerAdapter = (ArrayAdapter) ArrayAdapter.createFromResource(this, R.array.providers, android.R.layout.simple_spinner_item); + providerSpinnerAdapter = (ArrayAdapter) new ArrayAdapter(this, android.R.layout.simple_spinner_item, ProviderStore.getInstance().getProviderNames()); providerSpinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); providerSpinner.setAdapter(providerSpinnerAdapter); @@ -304,7 +305,13 @@ public void onCancel(DialogInterface dialog) { protected Boolean doInBackground(Account... params) { account = params[0]; try { - account.getProvider().createFetcher().testUsernameAndPassword(account); + ProviderFetcher f = account.getProvider().createFetcher(getApplicationContext()); + try { + f.testUsernameAndPassword(account); + } finally { + f.cleanup(); + } + return true; } catch (WrongPasswordException e) { errorMessage = "Username or password wrong"; diff --git a/src/com/eightbitcloud/internode/BrowserScraperActivity.java b/src/com/eightbitcloud/internode/BrowserScraperActivity.java new file mode 100644 index 0000000..38b53ef --- /dev/null +++ b/src/com/eightbitcloud/internode/BrowserScraperActivity.java @@ -0,0 +1,55 @@ +package com.eightbitcloud.internode; + +import android.app.Activity; +import android.os.Bundle; +import android.text.Editable; +import android.view.View; +import android.view.View.OnClickListener; +import android.webkit.WebView; +import android.widget.Button; +import android.widget.EditText; + +public class BrowserScraperActivity extends Activity { + + private WebView mWebView; + EditText edittext; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.browserscraper); + + + + + mWebView = (WebView) findViewById(R.id.webview); + mWebView.getSettings().setJavaScriptEnabled(true); + mWebView.setWebViewClient(new BrowserScraperWebViewClient()); + mWebView.loadUrl("http://www.google.com"); + + + + edittext = (EditText) findViewById(R.id.urlEntry); + + Button goButton = (Button) findViewById(R.id.goButton); + goButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + navigateToPage(edittext.getText().toString()); + } + + }); + + } + + private void navigateToPage(String url) { + if (url.startsWith("http://") || url.startsWith("https://")) { + edittext.setText(url); + mWebView.loadUrl(url); + } else { + navigateToPage("http://" + url); + } + + } + +} diff --git a/src/com/eightbitcloud/internode/BrowserScraperWebViewClient.java b/src/com/eightbitcloud/internode/BrowserScraperWebViewClient.java new file mode 100644 index 0000000..8438fc4 --- /dev/null +++ b/src/com/eightbitcloud/internode/BrowserScraperWebViewClient.java @@ -0,0 +1,13 @@ +package com.eightbitcloud.internode; + +import android.webkit.WebView; +import android.webkit.WebViewClient; + +public class BrowserScraperWebViewClient extends WebViewClient { + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + view.loadUrl(url); + return true; + } + +} diff --git a/src/com/eightbitcloud/internode/DataFetcher.java b/src/com/eightbitcloud/internode/DataFetcher.java index 003c824..e27ec54 100644 --- a/src/com/eightbitcloud/internode/DataFetcher.java +++ b/src/com/eightbitcloud/internode/DataFetcher.java @@ -2,17 +2,16 @@ package com.eightbitcloud.internode; import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.concurrent.FutureTask; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; import android.app.Application; import android.content.Context; import android.content.SharedPreferences; +import android.os.AsyncTask; import android.os.Handler; import android.preference.PreferenceManager; import android.util.Log; @@ -20,7 +19,6 @@ import com.eightbitcloud.internode.data.Account; import com.eightbitcloud.internode.data.Service; import com.eightbitcloud.internode.data.ServiceIdentifier; -import com.eightbitcloud.internode.provider.AccountUpdateException; import com.eightbitcloud.internode.provider.ProviderFetcher; import com.eightbitcloud.internode.provider.ServiceUpdateDetails; @@ -46,7 +44,7 @@ public class DataFetcher { // BackgroundSaver backgroundSaver; - ThreadPoolExecutor threadRunner = new ThreadPoolExecutor(0, 5, 60, TimeUnit.SECONDS, new LinkedBlockingQueue()); + List runningTasks = Collections.synchronizedList(new ArrayList()); boolean ignoringPreferenceUpdates = false; @@ -118,17 +116,20 @@ public List getAllServices() { } - private void startFetch(Runnable r) { - threadRunner.execute(new FutureTask(r, null)); - } public void cancelRunningFetches() { - for (Runnable r: threadRunner.getQueue()) { - @SuppressWarnings("unchecked") - FutureTask ft = (FutureTask) r; - ft.cancel(true); + + Log.i(NodeUsage.TAG, "Canceling Running fetches. There are " + runningTasks.size() + " active tasks"); + synchronized (runningTasks) { + for (AccountUpdater a: runningTasks) { + a.cancel(true); + } + runningTasks.clear(); } - threadRunner.purge(); + } + + public void notifyTaskFinished(AccountUpdater t) { + runningTasks.remove(t); } @@ -209,96 +210,116 @@ public void updateAccounts() { Log.i(NodeUsage.TAG, "Updating Services"); cancelRunningFetches(); for (Account account: accounts) { - startFetch(new ServiceFetcher(account)); + AccountUpdater u = new AccountUpdater(account); + runningTasks.add(u); + u.execute(account); } } - private abstract class UpdateTask implements Runnable { - protected T target; - - public UpdateTask(T target) { - this.target = target; + private class AccountUpdater extends AsyncTask { + private Account account; + private ProviderFetcher fetcher; + + public AccountUpdater(Account acct) { + this.account = acct; } - -// public T getTarget() { -// return target; -// } - - public void run() { + + @Override + protected Void doInBackground(Account... params) { + fetcher = account.getProvider().createFetcher(context); try { - handler.post(new Runnable() { - public void run() { - before(); - } - }); - final Y val = execute(); -// backgroundSaver.scheduleSave(); - handler.post(new Runnable() { - public void run() { - try { - after(val); - } catch (final Exception ex) { - handler.post(new Runnable() { - public void run() { - error(ex); - } - }); - } + boolean extraLogging = PreferenceManager.getDefaultSharedPreferences(context).getBoolean("performExtraLogging", false); + fetcher.setLogTraffic(extraLogging); + + try { + publishProgress(new Notification(Event.START_ACCOUNT_UPDATE, account)); + List results; + results = fetcher.fetchAccountUpdates(account); + publishProgress(new Notification(Event.COMPLETE_ACCOUNT_UPDATE, results)); + + // Now we update the usage for each service. + for (Service service : account.getAllServices()) { + if (!isCancelled()) { + try { + publishProgress(new Notification(Event.START_SERVICE_USAGE_UPDATE, service)); + Service clone = service.createUpdateClone(); + fetcher.fetchServiceDetails(clone); + publishProgress(new Notification(Event.COMPLETE_SERVICE_USAGE_UPDATE, new Service[] {service, clone})); + } catch (InterruptedException e) { + Log.e(NodeUsage.TAG, "Interrupted while updating Service: ", e); + publishProgress(new Notification(Event.ERROR_IN_SERVICE_USAGE_UPDATE, service, e)); + } catch (Exception e) { + Log.e(NodeUsage.TAG, "Error updating Service: ", e); + publishProgress(new Notification(Event.ERROR_IN_SERVICE_USAGE_UPDATE, service, e)); + } + } } - }); - } catch (final InterruptedException ex) { - // Thats okay. Its fine even!!! - // TODO does this need to notify that it was cancelled, therefore turning off downloading notifications and the like? - } catch (final Exception ex) { - handler.post(new Runnable() { - public void run() { - error(ex); - } - }); + } catch (InterruptedException e) { + Log.w(NodeUsage.TAG, "Interrupted fetching account " + account); + publishProgress(new Notification(Event.ERROR_IN_ACCOUNT_UPDATE, account, e)); + } catch (Exception e) { + Log.e(NodeUsage.TAG, "Error updating Accounts: ", e); + publishProgress(new Notification(Event.ERROR_IN_ACCOUNT_UPDATE, account, e)); + } + + + } finally { + fetcher.cleanup(); } + return null; } - public abstract void before(); - public abstract Y execute() throws Exception; - public abstract void after(Y val) throws AccountUpdateException; - public abstract void error(Exception ex); - } - - private class ServiceFetcher extends UpdateTask> { - private ProviderFetcher fetcher; - - public ServiceFetcher(Account account) { - super(account); - } - + @SuppressWarnings("unchecked") @Override - public void before() { - for (AccountUpdateListener l : listeners) { - l.startingServiceFetchForAccount(target); + protected void onProgressUpdate(Notification...details) { + for (Notification n: details) { + switch (n.evt) { + case START_ACCOUNT_UPDATE: + Account account = (Account) n.obj; + for (AccountUpdateListener l : listeners) { + l.startingServiceFetchForAccount(account); + } + break; + case COMPLETE_ACCOUNT_UPDATE: + notifyNewServiceList((List) n.obj); + break; + case ERROR_IN_ACCOUNT_UPDATE: + for (AccountUpdateListener l : listeners) { + l.errorUpdatingServices((Account) n.obj, n.ex); + } + break; + + case START_SERVICE_USAGE_UPDATE: + for (AccountUpdateListener l: listeners) { + l.serviceUpdated((Service) n.obj); + } + break; + case COMPLETE_SERVICE_USAGE_UPDATE: + Service[] x = (Service[]) n.obj; + updateService(x[0], x[1]); + break; + case ERROR_IN_SERVICE_USAGE_UPDATE: + for (AccountUpdateListener l : listeners) { + l.errorUpdatingService((Service) n.obj, n.ex); + } + break; + } } } - - @Override - public List execute() throws Exception { - fetcher = target.getProvider().createFetcher(); - boolean extraLogging = PreferenceManager.getDefaultSharedPreferences(context).getBoolean("performExtraLogging", false); - fetcher.setLogTraffic(extraLogging); - return fetcher.fetchAccountUpdates(target); - } - - @Override - public void after(List details) throws AccountUpdateException { - - Set oldServices = new HashSet(target.getAllServices()); + + + + private void notifyNewServiceList(List details) { + Set oldServices = new HashSet(account.getAllServices()); for (ServiceUpdateDetails u: details) { ServiceIdentifier accountNumber = u.getIdentifier(); - Service service = target.getService(accountNumber); + Service service = account.getService(accountNumber); if (service == null) { service = new Service(); service.setIdentifier(accountNumber); - target.addService(service); + account.addService(service); } service.addProperties(u.getProperties()); //TODO Should we clear existing ones first @@ -309,109 +330,51 @@ public void after(List details) throws AccountUpdateExcept oldServices.remove(service); } for (Service service: oldServices) { - target.removeService(service); + account.removeService(service); } allServices = null; for (AccountUpdateListener l : listeners) { - l.fetchedServiceNamesForAccount(target); + l.fetchedServiceNamesForAccount(account); } - for (Service service : target.getAllServices()) { - startFetch(new ServiceUpdaterTask(service, fetcher)); + // Notify all the services that we are staring the update. + for (Service service : account.getAllServices()) { + + for (AccountUpdateListener l : listeners) { + l.serviceUpdateStarted(service); + } } - } - @Override - public void error(Exception ex) { - allServices = null; - for (AccountUpdateListener l : listeners) { - l.errorUpdatingServices(target, ex); - } } - } - private class ServiceUpdaterTask extends UpdateTask { - private ProviderFetcher fetcher; + protected void updateService(Service service, Service serviceWithUpdates) { + service.updateFrom(serviceWithUpdates); + service.setLastUpdate(new Date()); - public ServiceUpdaterTask(Service service, ProviderFetcher fetcher) { - super(service); - this.fetcher = fetcher; - } - - @Override - public void before() { + // Update From cloned service. for (AccountUpdateListener l : listeners) { - l.serviceUpdateStarted(target); + l.serviceUpdated(service); } } - - @Override - public Service execute() throws Exception { - Service clone = target.createUpdateClone(); - fetcher.fetchServiceDetails(clone); - return clone; - } - - @Override - public void after(Service serviceWithUpdates) { - // Apply the service changes - target.updateFrom(serviceWithUpdates); - allServices = null; - for (AccountUpdateListener l : listeners) { - l.serviceUpdated(target); - } + } + + public static enum Event { START_ACCOUNT_UPDATE, COMPLETE_ACCOUNT_UPDATE, ERROR_IN_ACCOUNT_UPDATE, START_SERVICE_USAGE_UPDATE, COMPLETE_SERVICE_USAGE_UPDATE, ERROR_IN_SERVICE_USAGE_UPDATE }; + public static class Notification { + Event evt; + Object obj; + Exception ex; + + public Notification(Event evt, Object obj) { + this(evt, obj, null); } - - @Override - public void error(Exception ex) { - allServices = null; - for (AccountUpdateListener l : listeners) { - l.errorUpdatingService(target, ex); - } + + public Notification(Event evt, Object obj, Exception ex) { + this.evt = evt; + this.obj = obj; + this.ex = ex; } } -// public class BackgroundSaver extends Thread { -// boolean running = true; -// long scheduledSaveTime = -1; -// -// public synchronized void scheduleSave() { -//// Log.i(NodeUsage.TAG, "Scheduling Saving preferences"); -// // Give it 5 seconds... It seems like a long time, but remember -// // that saving will happen anyway. -// this.scheduledSaveTime = System.currentTimeMillis() + 5000; -// notify(); -// } -// -// public synchronized void shutdown() { -// this.running = false; -// notify(); -// } -// -// @Override -// public synchronized void run() { -// while (running) { -// long now = System.currentTimeMillis(); -// -// if (scheduledSaveTime >= 0 && scheduledSaveTime <= now) { -// saveState(); -// scheduledSaveTime = -1; -// } -// -// try { -// if (scheduledSaveTime >= 0) { -// backgroundSaver.wait(scheduledSaveTime - now); -// } else { -// backgroundSaver.wait(); // wait until we're interrupted -// } -// } catch (InterruptedException ex) { -// } -// } -// // One final save -// saveState(); -// Log.i(NodeUsage.TAG, "Background state saver finished"); -// } -// } } diff --git a/src/com/eightbitcloud/internode/GraphView.java b/src/com/eightbitcloud/internode/GraphView.java index 7f9fca2..a8f8aed 100644 --- a/src/com/eightbitcloud/internode/GraphView.java +++ b/src/com/eightbitcloud/internode/GraphView.java @@ -10,6 +10,7 @@ import android.graphics.Paint; import android.graphics.RectF; import android.util.AttributeSet; +import android.util.Log; import android.view.View; import com.eightbitcloud.internode.data.Value; @@ -22,6 +23,7 @@ public class GraphView extends View { private Value maxValue; + private Value totalValue; Value thresholdValue; @SuppressWarnings("unchecked") @@ -101,11 +103,18 @@ public void setGraphColors(GraphColors colors) { public void setData(GraphData data) { this.data = data; maxValue = null; + totalValue = null; for (Value[] record: data.data.values()) { Value sum = sumValues(record); if (maxValue == null || sum.isGreaterThan(maxValue)) { maxValue = sum; } + + if (totalValue == null) { + totalValue = sum; + } else { + totalValue = totalValue.plus(sum); + } } requestLayout(); @@ -239,7 +248,9 @@ private void drawPieGraph(Canvas canvas) { int paintIndex =0; float y = y1+20; for (Object key: data.xaxisData) { - float pos =(float) (360*data.data.get(key)[0].divideByValue(maxValue)); + float pos =(float) (360*data.data.get(key)[0].divideByValue(totalValue)); +// Log.d(NodeUsage.TAG, data.data.get(key)[0] + " divided by " + totalValue + " is " + data.data.get(key)[0].divideByValue(totalValue) + " or " + pos); + Paint c = graphColors.barPaint[paintIndex % graphColors.barPaint.length]; canvas.drawArc(oval, oldpos, pos, true, c); diff --git a/src/com/eightbitcloud/internode/NodeDroidWidgetConfigure.java b/src/com/eightbitcloud/internode/NodeDroidWidgetConfigure.java new file mode 100644 index 0000000..414caf8 --- /dev/null +++ b/src/com/eightbitcloud/internode/NodeDroidWidgetConfigure.java @@ -0,0 +1,54 @@ +package com.eightbitcloud.internode; + +import android.app.Activity; +import android.appwidget.AppWidgetManager; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.RemoteViews; + +public class NodeDroidWidgetConfigure extends Activity { + int mAppWidgetId; + Intent resultValue = new Intent(); + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.widgetconfig); + + Intent intent = getIntent(); + Bundle extras = intent.getExtras(); + if (extras != null) { + mAppWidgetId = extras.getInt( + AppWidgetManager.EXTRA_APPWIDGET_ID, + AppWidgetManager.INVALID_APPWIDGET_ID); + } + + resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId); + setResult(RESULT_CANCELED, resultValue); + + Button saveButton = (Button) findViewById(R.id.configSave); + saveButton.setOnClickListener(new OnClickListener() { + + public void onClick(View v) { + updateWidget(); + } + }); + + } + + public void updateWidget() { + AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this); + + RemoteViews views = new RemoteViews(this.getPackageName(), R.layout.usagewidget); + appWidgetManager.updateAppWidget(mAppWidgetId, views); + + setResult(RESULT_OK, resultValue); + finish(); + + } + +} diff --git a/src/com/eightbitcloud/internode/NodeDroidWidgetProvider.java b/src/com/eightbitcloud/internode/NodeDroidWidgetProvider.java new file mode 100644 index 0000000..04295e2 --- /dev/null +++ b/src/com/eightbitcloud/internode/NodeDroidWidgetProvider.java @@ -0,0 +1,55 @@ +package com.eightbitcloud.internode; + +import android.app.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProvider; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.widget.RemoteViews; + +public class NodeDroidWidgetProvider extends AppWidgetProvider { + + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + final int N = appWidgetIds.length; + + // Perform this loop procedure for each App Widget that belongs to this provider + for (int i=0; i parent, View view, int position, long id) } + public void refreshLastUpdated() { + TextView lastUpdatedText = (TextView) findViewById(R.id.lastUpdated); + lastUpdatedText.setText(formatTimeAgo(service.getLastUpdate())); + } + + public void setService(Service service) { this.service = service; listAdapter.setService(service); + ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress); + ImageView imageView = (ImageView) findViewById(R.id.pendingImage); + switch (service.getUpdateStatus()) { + case IDLE: + progressBar.setVisibility(View.INVISIBLE); + imageView.setVisibility(View.INVISIBLE); + break; + case PENDING_UPDATE: + progressBar.setVisibility(View.INVISIBLE); + imageView.setVisibility(View.VISIBLE); + break; + case UPDATING: + progressBar.setVisibility(View.VISIBLE); + imageView.setVisibility(View.INVISIBLE); + break; + + } + refreshLastUpdated(); + Provider prov = service.getAccount().getProvider(); int resid = getResources().getIdentifier(prov.getBackgroundResource(), "drawable", this.getClass().getPackage().getName()); TextView serviceID = (TextView) findViewById(R.id.serviceid); @@ -82,30 +110,43 @@ public void setService(Service service) { update(); } - public Service getService() { - return this.service; - } - - - public synchronized void setLoading(boolean loading) { + private String formatTimeAgo(Date lastUpdate) { + if (lastUpdate == null) { + return "never updated"; + } + long diff = System.currentTimeMillis() - lastUpdate.getTime(); - boolean runningBefore = this.loading; - this.loading = loading; - boolean runningAfterwards = loading; - - - if (runningBefore ^ runningAfterwards) { - ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress); - if (runningBefore) { - // We're turing it off - progressBar.setVisibility(View.INVISIBLE); + long seconds = diff / 1000; + if (seconds < 60) { + return "updated moments ago"; + } else { + long minutes = seconds / 60; + if (minutes == 1) { + return "updated 1 minute ago"; + } else if (minutes < 60) { + return "updated " + minutes + " minutes ago"; } else { - // We're turning it on. - progressBar.setVisibility(View.VISIBLE); + long hours = minutes / 60; + if (hours == 1) { + return "updated 1 hour ago"; + } else if (hours < 24) { + return "updated " + hours + " hours ago"; + } else { + long days = hours / 24; + if (days == 1) { + return "updated " + days + " day ago"; + } else { + return "updated " + days + " days ago"; + } + } } } } + public Service getService() { + return this.service; + } + public void updateGraph(MetricGroup mg) { @@ -177,7 +218,7 @@ public void update() { } String remainder; - if (daysLeft == 0) { + if (daysLeft <= 0) { remainder = remainingQuota.toString(); } else { remainder = remainingQuota.divideByNumber(daysLeft).toString() + "/day"; @@ -223,4 +264,5 @@ static class ViewHolder { } + } diff --git a/src/com/eightbitcloud/internode/UsageGraphType.java b/src/com/eightbitcloud/internode/UsageGraphType.java index ee553ad..496edc0 100644 --- a/src/com/eightbitcloud/internode/UsageGraphType.java +++ b/src/com/eightbitcloud/internode/UsageGraphType.java @@ -3,7 +3,8 @@ public enum UsageGraphType { BREAKDOWN("Breakdown"), MONTHLY_USAGE("Month"), - YEARLY_USAGE("Year"); + YEARLY_USAGE("Year"), + ALL_USAGE("All"); private String displayName; diff --git a/src/com/eightbitcloud/internode/UsageGraphView.java b/src/com/eightbitcloud/internode/UsageGraphView.java index 487716f..53497ce 100644 --- a/src/com/eightbitcloud/internode/UsageGraphView.java +++ b/src/com/eightbitcloud/internode/UsageGraphView.java @@ -23,6 +23,7 @@ import com.eightbitcloud.internode.data.Plan; import com.eightbitcloud.internode.data.UsageRecord; import com.eightbitcloud.internode.data.Value; +import com.eightbitcloud.internode.util.DateTools; public class UsageGraphView extends LinearLayout { /** @@ -127,6 +128,8 @@ public void setSelectedGraph(UsageGraphType usageGraphType) { case YEARLY_USAGE: generateYearly(); break; + case ALL_USAGE: + generateAll(); } @@ -233,39 +236,82 @@ private void generateMonthly() { } + public Date[] getDateBounds(List values) { + Date lastDate = null; + Date firstDate = null; + + for (MeasuredValue v: values) { + if (v.getUsageRecords().size() > 0) { + Date last = v.getUsageRecords().lastKey(); + if (lastDate == null) { + lastDate = last; + } else { + if (last.after(lastDate)) + lastDate = last; + } + + Date first = v.getUsageRecords().firstKey(); + if (firstDate == null) { + firstDate = first; + } else { + if (first.before(firstDate)) { + firstDate = first; + } + } + } + } + return new Date[] { firstDate, lastDate}; + + } + + private void generateAll() { + SortedMap results = new TreeMap(); + + + List values = getOrderedValues(); + Date[] dateBounds = getDateBounds(values); + + if (dateBounds[0] != null) { + Calendar cal = Calendar.getInstance(); // TODO make it respect the TZ + Date periodStart = DateTools.ensureMidnight(dateBounds[0]); + cal.setTime(DateTools.ensureMidnight(dateBounds[1])); // TODO there is shared code here between this and generateMonthly (and yearly for that matter) Generalise it + cal.add(Calendar.DAY_OF_MONTH, 1); + Date terminalDate = cal.getTime(); + + cal.setTime(periodStart); + int count = group.getComponents().size(); + + while (periodStart.before(terminalDate)) { + cal.setTime(periodStart); + cal.add(Calendar.DAY_OF_MONTH, 1); + Date periodEnd = cal.getTime(); + + int index = 0; + for (MeasuredValue mv: values) { + SortedMap usage = mv.getUsageRecords(); + Value sum = new Value(0, mv.getUnits()); + SortedMap monthEntries = usage.subMap(periodStart, periodEnd); + for (UsageRecord dayValue : monthEntries.values()) { + sum = sum.plus(dayValue.getAmount()); + } + + Value[] entries = results.get(periodStart); + if (entries == null) { + entries = new Value[count]; + results.put(periodStart, entries); + } + entries[index] = sum; + index++; + } + periodStart = periodEnd; + } + graph.setData(new GraphData(results, GraphStyle.BAR, DateKeyFormatter.MONTH_FORMATTER)); + graph.thresholdValue = null; + } + } + + -// -// private void generateMonthly() { -// -// -// Calendar cal = Calendar.getInstance(); -// cal.setTime(new Date()); -// cal.add(Calendar.MONTH, -1); -// cal.set(Calendar.HOUR_OF_DAY, 23); -// cal.set(Calendar.MINUTE, 59); -// cal.set(Calendar.SECOND, 59); -// cal.set(Calendar.MILLISECOND, 999); -// -// SortedMap graphData = new TreeMap(); -// int index = 0; -// int count = group.getComponents().size(); -// for (MeasuredValue mv: group.getComponents()) { -// SortedMap usage = mv.getUsageRecords(); -// SortedMap lastMonth = usage.tailMap(cal.getTime()); -// for (Map.Entry e: lastMonth.entrySet()) { -// Value[] entries = graphData.get(e.getKey()); -// if (entries == null) { -// entries = new Value[count]; -// graphData.put(e.getKey(), entries); -// } -// entries[index] = e.getValue().getAmount(); -// } -// index++; -// } -// -// graph.setData(new GraphData(graphData, GraphStyle.BAR, DateKeyFormatter.MONTH_FORMATTER)); -// graph.thresholdValue = null; -// } private void generateBreakdown() { List values = getOrderedValues(); diff --git a/src/com/eightbitcloud/internode/data/Account.java b/src/com/eightbitcloud/internode/data/Account.java index d4c3491..a7f593f 100644 --- a/src/com/eightbitcloud/internode/data/Account.java +++ b/src/com/eightbitcloud/internode/data/Account.java @@ -1,5 +1,6 @@ package com.eightbitcloud.internode.data; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -10,11 +11,11 @@ public class Account extends ThingWithProperties { private Provider provider; private Map services = new HashMap(); - public Service getService(ServiceIdentifier accountID) { + public synchronized Service getService(ServiceIdentifier accountID) { return services.get(accountID); } - public void addService(Service service) { + public synchronized void addService(Service service) { service.setAccount(this); this.services.put(service.getIdentifier(), service); } @@ -43,11 +44,11 @@ public void setProvider(Provider provider) { this.provider = provider; } - public Collection getAllServices() { - return services.values(); + public synchronized Collection getAllServices() { + return new ArrayList(services.values()); } - public boolean removeService(Service service) { + public synchronized boolean removeService(Service service) { Service removed = services.remove(service.getIdentifier()); if (removed != null) { removed.setAccount(null); diff --git a/src/com/eightbitcloud/internode/data/Plan.java b/src/com/eightbitcloud/internode/data/Plan.java index aa1aa76..e55a421 100644 --- a/src/com/eightbitcloud/internode/data/Plan.java +++ b/src/com/eightbitcloud/internode/data/Plan.java @@ -109,8 +109,12 @@ public void writeTo(JSONObject obj) throws JSONException { super.writeTo(obj); obj.put(NAME2, name); obj.put(DESCRIPTION2, description); - obj.put(ROLLOVER, rolloverDate.getTime()); - obj.put(COST2, cost.getPrefValue()); + if (rolloverDate != null) { + obj.put(ROLLOVER, rolloverDate.getTime()); + } + if (cost != null) { + obj.put(COST2, cost.getPrefValue()); + } obj.put(INTERVAL2, interval); obj.put(EXTRAS2, PreferencesSerialiser.createJSONRepresentationForStrings(extras)); } @@ -120,8 +124,13 @@ public void readFrom(JSONObject obj) throws JSONException { super.readFrom(obj); name = obj.getString(NAME2); description = obj.has(DESCRIPTION2) ? obj.getString(DESCRIPTION2) : null; - rolloverDate = new Date(obj.getLong(ROLLOVER)); - cost = new Value(obj.getString(COST2)); + if (obj.has(ROLLOVER)) { + rolloverDate = new Date(obj.getLong(ROLLOVER)); + } + + if (obj.has(COST2)) { + cost = new Value(obj.getString(COST2)); + } interval = obj.has(INTERVAL2) ? PlanInterval.valueOf(obj.getString(INTERVAL2)) : null; extras = PreferencesSerialiser.createStringArray(obj.getJSONArray(EXTRAS2)); } diff --git a/src/com/eightbitcloud/internode/data/Provider.java b/src/com/eightbitcloud/internode/data/Provider.java index fcdd3a6..73df912 100644 --- a/src/com/eightbitcloud/internode/data/Provider.java +++ b/src/com/eightbitcloud/internode/data/Provider.java @@ -4,11 +4,13 @@ import java.util.HashMap; import java.util.Map; +import android.content.Context; + import com.eightbitcloud.internode.GraphColors; +import com.eightbitcloud.internode.provider.InternodeFetcher; +import com.eightbitcloud.internode.provider.OptusFetcher; import com.eightbitcloud.internode.provider.ProviderFetcher; import com.eightbitcloud.internode.provider.VodafoneMBBFetcher; -import com.eightbitcloud.internode.provider.internode.InternodeFetcher; -import com.eightbitcloud.internode.provider.optus.OptusFetcher; public class Provider { private String name; @@ -70,14 +72,14 @@ public void setLogoURL(String logoURL) { this.logoURL = logoURL; } - public ProviderFetcher createFetcher() { + public ProviderFetcher createFetcher(Context ctx) { if (name.equalsIgnoreCase("internode")) { KeyStore ts = (KeyStore) ProviderStore.getInstance().getProvider("internode").getProperty("keyStore"); - return new InternodeFetcher(this,ts); + return new InternodeFetcher(this, ctx, ts); } else if (name.equalsIgnoreCase("Vodafone MBB")) { - return new VodafoneMBBFetcher(this); + return new VodafoneMBBFetcher(this, ctx); } else if (name.equalsIgnoreCase("optus mobile")) { - return new OptusFetcher(this); + return new OptusFetcher(this, ctx); } else return null; } diff --git a/src/com/eightbitcloud/internode/data/ProviderStore.java b/src/com/eightbitcloud/internode/data/ProviderStore.java index 174246a..b9a8381 100644 --- a/src/com/eightbitcloud/internode/data/ProviderStore.java +++ b/src/com/eightbitcloud/internode/data/ProviderStore.java @@ -5,6 +5,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -13,7 +14,7 @@ public class ProviderStore { private static ProviderStore store; - Map providers = new HashMap(); + LinkedHashMap providers = new LinkedHashMap(); public ProviderStore() { // TODO make this come from an xml file. @@ -47,15 +48,11 @@ public ProviderStore() { vodafoneMBBProv.setBeta(true); vodafoneMBBProv.setLogoURL("https://secure.broadband.vodafone.com.au/CRMVOD/img/vodaLogo.jpg"); vodafoneMBBProv.setUrl("http://www.vodafone.com.au/"); - vodafoneMBBProv.setTextColour("#006685"); - vodafoneMBBProv.setGraphColors(new GraphColors(0xff000000, 0xff006685, -// 0xffFFE77F, 0xff674C99, 0xff807299, 0xff807299, 0xffFFD000 - 0xffFF9D00, - 0xffF6FF00, 0xffA97E38, - 0xffFFCE7F, 0xffFFD000 + vodafoneMBBProv.setTextColour("#ffffff"); + vodafoneMBBProv.setGraphColors(new GraphColors(0xff000000, 0xff000000, 0xffff3939 )); vodafoneMBBProv.setBackgroundResource("vodaheader"); //#fe0000 -// addProvider(vodafoneMBBProv); + addProvider(vodafoneMBBProv); } @@ -86,4 +83,8 @@ public int compare(Provider object1, Provider object2) { }); return result; } + + public String[] getProviderNames() { + return new ArrayList(providers.keySet()).toArray(new String[0]); + } } diff --git a/src/com/eightbitcloud/internode/data/Service.java b/src/com/eightbitcloud/internode/data/Service.java index a01576f..704cd56 100644 --- a/src/com/eightbitcloud/internode/data/Service.java +++ b/src/com/eightbitcloud/internode/data/Service.java @@ -1,5 +1,6 @@ package com.eightbitcloud.internode.data; +import java.util.Date; import java.util.List; import org.json.JSONException; @@ -27,10 +28,14 @@ public class Service extends ThingWithProperties implements PreferencesSerialisa private static final String PLAN2 = "plan"; private static final String IDACCT = "idacct"; private static final String IDPROV = "idprov"; + private static final String LASTUPDATE = "lastUpdate"; private transient Account account; private ServiceIdentifier identifier; private Plan plan; private NameMappedList metricGroups = new NameMappedList(); + + private Date lastUpdate; + private UpdateStatus updateStatus = UpdateStatus.IDLE; public Service createUpdateClone() { @@ -48,6 +53,7 @@ public Service createUpdateClone() { public void updateFrom(Service u) { this.plan = u.plan; this.metricGroups = u.metricGroups; + this.account = u.account; addProperties(u.getProperties()); // Wow, that was easy.... } @@ -134,6 +140,7 @@ public void writeTo(JSONObject obj) throws JSONException { obj.put(IDACCT, identifier.getAccountNumber()); obj.put(PLAN2, plan == null ? null : PreferencesSerialiser.createJSONRepresentation(plan)); obj.put(MG, PreferencesSerialiser.createJSONRepresentation(metricGroups)); + obj.put(LASTUPDATE, lastUpdate == null ? -1: lastUpdate.getTime()); } @Override @@ -146,6 +153,27 @@ public void readFrom(JSONObject obj) throws JSONException { for (MetricGroup mg: this.metricGroups) { mg.__setService(this); } + lastUpdate = obj.has(LASTUPDATE) ? new Date(obj.getLong(LASTUPDATE)) : null; + } + + + public Date getLastUpdate() { + return lastUpdate; + } + + + public void setLastUpdate(Date lastUpdate) { + this.lastUpdate = lastUpdate; + } + + + public UpdateStatus getUpdateStatus() { + return updateStatus; + } + + + public void setUpdateStatus(UpdateStatus updateStatus) { + this.updateStatus = updateStatus; } diff --git a/src/com/eightbitcloud/internode/data/UpdateStatus.java b/src/com/eightbitcloud/internode/data/UpdateStatus.java new file mode 100644 index 0000000..ac5bad5 --- /dev/null +++ b/src/com/eightbitcloud/internode/data/UpdateStatus.java @@ -0,0 +1,5 @@ +package com.eightbitcloud.internode.data; + +public enum UpdateStatus { + IDLE, PENDING_UPDATE, UPDATING; +} diff --git a/src/com/eightbitcloud/internode/data/UsageRecord.java b/src/com/eightbitcloud/internode/data/UsageRecord.java index d0ba0ee..7fb061d 100644 --- a/src/com/eightbitcloud/internode/data/UsageRecord.java +++ b/src/com/eightbitcloud/internode/data/UsageRecord.java @@ -67,4 +67,9 @@ public void readFrom(JSONObject obj) throws JSONException { } + @Override + public String toString() { + return "UsageRecord [time=" + time + ", amount=" + amount + ", cost=" + cost + ", description=" + description + "]"; + } + } diff --git a/src/com/eightbitcloud/internode/provider/AbstractFetcher.java b/src/com/eightbitcloud/internode/provider/AbstractFetcher.java index db5aca6..3b519d0 100644 --- a/src/com/eightbitcloud/internode/provider/AbstractFetcher.java +++ b/src/com/eightbitcloud/internode/provider/AbstractFetcher.java @@ -1,41 +1,105 @@ package com.eightbitcloud.internode.provider; import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; import java.net.URLDecoder; +import java.security.KeyStore; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.scheme.PlainSocketFactory; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.cookie.Cookie; import org.apache.http.entity.BufferedHttpEntity; import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.conn.SingleClientConnManager; +import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.HttpParams; import org.apache.http.params.HttpProtocolParams; import org.apache.http.util.EntityUtils; +import android.content.Context; import android.util.Log; import com.eightbitcloud.internode.NodeUsage; import com.eightbitcloud.internode.data.Provider; public abstract class AbstractFetcher implements ProviderFetcher { - private static final int MAX_LOG_LENGTH = 4000; protected Provider provider; protected boolean logTraffic = false; private DefaultHttpClient httpClient; + private Context context; - public AbstractFetcher(Provider provider) { + + private ThreadLocal logWriter = new ThreadLocal() { + @Override + protected synchronized PrintWriter initialValue() { + DateFormat f = new SimpleDateFormat("yyyyMMddHHmmss"); + try { + return new PrintWriter(new OutputStreamWriter(context.openFileOutput( + "FetcherLog-" + provider.getName().replace(' ', '_') + '-' + f.format(new Date()) + '-' + Thread.currentThread().getId() + ".txt", Context.MODE_APPEND))); + } catch (FileNotFoundException e) { + e.printStackTrace(); + return null; + } + } + }; + + + public AbstractFetcher(Provider provider, Context ctx) { this.provider = provider; + this.context = ctx; + } + + protected HttpParams createHttpParams() { + HttpParams params = new BasicHttpParams(); + params.setParameter(HttpProtocolParams.USER_AGENT, "NodeDroid/2.02 (Android Usage Meter )"); + return params; + } + + protected SchemeRegistry createSchemeRegistry() { + try { + SchemeRegistry sr = new SchemeRegistry(); + sr.register(new Scheme("http", new PlainSocketFactory(), 80)); + + SSLSocketFactory sf = SSLSocketFactory.getSocketFactory(); + Scheme httpsScheme = new Scheme("https", sf, 443); + sr.register(httpsScheme); + + return sr; + + } catch (Exception ex) { + // Unlikely to happen + return null; + } + } protected DefaultHttpClient createHttpClient() { - DefaultHttpClient client = new DefaultHttpClient(); - client.getParams().setParameter(HttpProtocolParams.USER_AGENT, "NodeDroid/2.02 (Android Usage Meter )"); - return client; +// try { + HttpParams params = createHttpParams(); + ClientConnectionManager cm = new SingleClientConnManager(params, createSchemeRegistry()); + params.setParameter(HttpProtocolParams.USER_AGENT, "NodeDroid/2.02 (Android Usage Meter )"); + return new DefaultHttpClient(cm, params); +// } catch (Exception e) { +// // This is pretty unlikely +// e.printStackTrace(); +// return null; +// } } @@ -90,7 +154,7 @@ protected HttpResponse executeThenCheckIfInterrupted(HttpRequestBase m, String.. } HttpResponse resp = httpClient.execute(m); - if (Thread.currentThread().isInterrupted()) { + if (Thread.interrupted()) { throw new InterruptedException(); } @@ -112,14 +176,24 @@ protected HttpResponse executeThenCheckIfInterrupted(HttpRequestBase m, String.. } - private void logTraffic(String x) { - if (x.length() > MAX_LOG_LENGTH) { - logTraffic(x.substring(0, MAX_LOG_LENGTH)); - logTraffic(x.substring(MAX_LOG_LENGTH)); - } else { - Log.d(NodeUsage.TAG, "HTTP" + Thread.currentThread().getId() + ": " + x); + private void logTraffic(String x) throws IOException { + + logWriter.get().println(x); +// if (x.length() > MAX_LOG_LENGTH) { +// logTraffic(x.substring(0, MAX_LOG_LENGTH)); +// logTraffic(x.substring(MAX_LOG_LENGTH)); +// } else { +// Log.d(NodeUsage.TAG, "HTTP" + Thread.currentThread().getId() + ": " + x); +// } + } + + + public void cleanup() { + if (logTraffic) { + logWriter.get().close(); } } + } diff --git a/src/com/eightbitcloud/internode/provider/internode/InternodeFetcher.java b/src/com/eightbitcloud/internode/provider/InternodeFetcher.java similarity index 92% rename from src/com/eightbitcloud/internode/provider/internode/InternodeFetcher.java rename to src/com/eightbitcloud/internode/provider/InternodeFetcher.java index 8f503ff..0766d19 100644 --- a/src/com/eightbitcloud/internode/provider/internode/InternodeFetcher.java +++ b/src/com/eightbitcloud/internode/provider/InternodeFetcher.java @@ -1,4 +1,4 @@ -package com.eightbitcloud.internode.provider.internode; +package com.eightbitcloud.internode.provider; import java.io.IOException; import java.net.MalformedURLException; @@ -32,6 +32,7 @@ import org.w3c.dom.NodeList; import org.xml.sax.SAXException; +import android.content.Context; import android.util.Log; import com.eightbitcloud.internode.NodeUsage; @@ -49,10 +50,6 @@ import com.eightbitcloud.internode.data.Unit; import com.eightbitcloud.internode.data.UsageRecord; import com.eightbitcloud.internode.data.Value; -import com.eightbitcloud.internode.provider.AbstractFetcher; -import com.eightbitcloud.internode.provider.AccountUpdateException; -import com.eightbitcloud.internode.provider.ServiceUpdateDetails; -import com.eightbitcloud.internode.provider.WrongPasswordException; import com.eightbitcloud.internode.util.DateTools; import com.eightbitcloud.internode.util.XMLTools; @@ -66,8 +63,8 @@ public class InternodeFetcher extends AbstractFetcher { private DocumentBuilderFactory dbf; private KeyStore trustStore; - public InternodeFetcher(Provider provider, KeyStore trustStore) { - super(provider); + public InternodeFetcher(Provider provider, Context ctx, KeyStore trustStore) { + super(provider, ctx); this.trustStore = trustStore; try { dbf = DocumentBuilderFactory.newInstance(); @@ -77,26 +74,23 @@ public InternodeFetcher(Provider provider, KeyStore trustStore) { e.printStackTrace(); } } - + @Override - public DefaultHttpClient createHttpClient() { + protected SchemeRegistry createSchemeRegistry() { try { SSLSocketFactory sf = new SSLSocketFactory(trustStore); Scheme httpsScheme = new Scheme("https", sf, 443); SchemeRegistry schemeRegistry = new SchemeRegistry(); schemeRegistry.register(httpsScheme); - - HttpParams params = new BasicHttpParams(); - ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry); - params.setParameter(HttpProtocolParams.USER_AGENT, "NodeDroid/2.02 (Android Usage Meter )"); - return new DefaultHttpClient(cm, params); - } catch (Exception e) { - // This is pretty unlikely - e.printStackTrace(); + + return schemeRegistry; + } catch (Exception ex) { + // Unlikely to happen return null; } } + public Element request(URL url, Account account) throws IOException, ParserConfigurationException, IllegalStateException, SAXException, InterruptedException { final HttpGet conn = new HttpGet(url.toString()); conn.addHeader("Authorization", "Basic " + new String(Base64.encodeBase64(new String(account.getUsername()+":"+account.getPassword()).getBytes()))); @@ -146,7 +140,7 @@ private URL getHistoryURL(Service service) throws MalformedURLException { - public void fetchServiceDetails(Service service) throws AccountUpdateException { + public void fetchServiceDetails(Service service) throws AccountUpdateException, InterruptedException { try { // Set up the Metric Group for the Account - at this stage, we only measure metered usage. MetricGroup mg = new MetricGroup(service, METRIC_GROUP, Unit.BYTE, CounterStyle.QUOTA); @@ -162,6 +156,8 @@ public void fetchServiceDetails(Service service) throws AccountUpdateException { updateServiceDetails(service); updateUsage(service); updateHistory(service); + } catch (InterruptedException ex) { + throw ex; } catch (Exception e) { throw new AccountUpdateException("Error updating usage for " + service, e); } @@ -259,7 +255,7 @@ private void updateHistory(Service service) throws IllegalStateException, Malfor - public List fetchAccountUpdates(Account account) throws AccountUpdateException, WrongPasswordException { + public List fetchAccountUpdates(Account account) throws AccountUpdateException, WrongPasswordException, InterruptedException { try { Element response = request(baseURL, account); NodeList services = XMLTools.getNode(XMLTools.getAPINode(response), "services").getElementsByTagName("service"); @@ -285,15 +281,19 @@ public List fetchAccountUpdates(Account account) throws Ac } catch ( WrongPasswordException ex) { throw ex; + } catch (InterruptedException ex) { + throw ex; } catch (final Exception ex) { throw new AccountUpdateException("Error loading services", ex); } } - public void testUsernameAndPassword(Account account) throws AccountUpdateException, WrongPasswordException { + public void testUsernameAndPassword(Account account) throws AccountUpdateException, WrongPasswordException, InterruptedException { try { request(baseURL, account); + } catch (InterruptedException ex) { + throw ex; } catch (WrongPasswordException ex) { throw ex; } catch (Exception e) { diff --git a/src/com/eightbitcloud/internode/provider/optus/OptusFetcher.java b/src/com/eightbitcloud/internode/provider/OptusFetcher.java similarity index 77% rename from src/com/eightbitcloud/internode/provider/optus/OptusFetcher.java rename to src/com/eightbitcloud/internode/provider/OptusFetcher.java index 45ff75d..7bd0f2e 100644 --- a/src/com/eightbitcloud/internode/provider/optus/OptusFetcher.java +++ b/src/com/eightbitcloud/internode/provider/OptusFetcher.java @@ -1,4 +1,4 @@ -package com.eightbitcloud.internode.provider.optus; +package com.eightbitcloud.internode.provider; import java.io.IOException; import java.math.BigDecimal; @@ -9,9 +9,11 @@ import java.util.Calendar; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -25,6 +27,7 @@ import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; +import android.content.Context; import android.util.Log; import com.eightbitcloud.internode.NodeUsage; @@ -40,10 +43,6 @@ import com.eightbitcloud.internode.data.Unit; import com.eightbitcloud.internode.data.UsageRecord; import com.eightbitcloud.internode.data.Value; -import com.eightbitcloud.internode.provider.AbstractFetcher; -import com.eightbitcloud.internode.provider.AccountUpdateException; -import com.eightbitcloud.internode.provider.ServiceUpdateDetails; -import com.eightbitcloud.internode.provider.WrongPasswordException; import com.eightbitcloud.internode.util.DateTools; public class OptusFetcher extends AbstractFetcher { @@ -51,6 +50,7 @@ public class OptusFetcher extends AbstractFetcher { public static final String FREE_DATA_GROUP = "Free Data"; public static final String FREE_CALLS_GROUP = "Free Calls"; public static final String DATA_PACK_GROUP = "Data"; + private static final String INTERMEDIATE_URL = "IntermediateUsageURL"; private static final String USAGE_URL = "UsageURL"; private static final String SMAGENTKEY = ")"); -// DefaultHttpClient client = new DefaultHttpClient(params); -// return client; -// } - public OptusFetcher(Provider provider) { - super(provider); + + public OptusFetcher(Provider provider, Context ctx) { + super(provider, ctx); melbourneTZ = TimeZone.getTimeZone("GMT+1000"); rolloverFormatter.setTimeZone(melbourneTZ); } -// private HttpResponse handleRedirect(HttpResponse resp) throws ClientProtocolException, InterruptedException, IOException { -// return handleRedirect(resp, 1); -// } -// private HttpResponse handleRedirect(HttpResponse resp, int count) throws ClientProtocolException, InterruptedException, IOException { -// if (count > 10) { -// throw new IOException("too many redirects"); -// } -// String newLocation = resp.getFirstHeader("Location").getValue(); -// Log.d(NodeUsage.TAG, "New location is " + newLocation); -// resp.getEntity().consumeContent(); -// resp = executeThenCheckIfInterrupted(new HttpGet(newLocation)); -// -// switch (resp.getStatusLine().getStatusCode()) { -// case HttpStatus.SC_MOVED_PERMANENTLY: -// case HttpStatus.SC_MOVED_TEMPORARILY: -// return handleRedirect(resp, count+1); -// case HttpStatus.SC_OK: -// return resp; -// default: -// throw new IOException("Expected OK Response, but got " + resp.getStatusLine()); -// } -// } private String login(Account account) throws AccountUpdateException, InterruptedException, WrongPasswordException { try { - HttpGet loginPage = new HttpGet("http://www.optus.com.au/login"); + HttpGet loginPage = new HttpGet("https://www.optus.com.au/login"); HttpResponse resp = executeThenCheckIfInterrupted(loginPage); if (resp.getStatusLine().getStatusCode() != HttpStatus.SC_OK) throw new IOException("Expected OK Response, got " + resp.getStatusLine().getStatusCode()); @@ -111,6 +96,11 @@ private String login(Account account) throws AccountUpdateException, Interrupted String body = EntityUtils.toString(resp.getEntity()); + + if (body.contains("making some improvements to My Account")) { + throw new AccountUpdateException("Usage Site offline for maintenance", null); + } + int pos = body.indexOf(SMAGENTKEY); if (pos == -1) { throw new IOException("Couldn't find key"); @@ -195,43 +185,61 @@ public List fetchAccountUpdates(Account account) throws Ac Pattern itemSeparatorPattern = Pattern.compile("
  • "); List itemSections = cutUp(loginResult, itemSeparatorPattern, m3.end()); + Set usageURLs = new HashSet(); for (String section: itemSections) { // Get the Phone number. // TODO technically there could be multiple of these, and it might be possible to have non-digits. - Pattern phoneNumberPattern = Pattern.compile("
      \\s*
    1. \\s*
      \\s*([0-9]+)\\s*
      \\s*
    2. "); - Matcher phoneNumberMatcher = phoneNumberPattern.matcher(section); - if (!phoneNumberMatcher.find()) - throw new IOException("Expected to find service items...."); - String phoneNumber = phoneNumberMatcher.group(1); - +// Pattern phoneNumberPattern = Pattern.compile("
        \\s*
      1. \\s*
        \\s*([0-9]+)\\s*
        \\s*
      2. "); +// Matcher phoneNumberMatcher = phoneNumberPattern.matcher(section); +// if (!phoneNumberMatcher.find()) +// throw new IOException("Expected to find service items...."); +// String phoneNumber = phoneNumberMatcher.group(1); +// + // The front page shows "show usage" for each service, but then the individual URLs are all on the next page. + // Weed out the duplicates by putting them in a set, then go fetch those pages. Pattern usageURLPattern = Pattern.compile("
        \\s*"); Matcher usageURLMatcher = usageURLPattern.matcher(section); - if (!usageURLMatcher.find()) - throw new IOException("Expected to find url for usage"); - String usageURL = prependHost(usageURLMatcher.group(1)); + while (usageURLMatcher.find()) { + String usageURL = prependHost(usageURLMatcher.group(1)); + usageURLs.add(usageURL); + } + } + + for (String intermediateUsageURL: usageURLs) { + HttpGet intermediatePage = new HttpGet(intermediateUsageURL); + HttpResponse resp = executeThenCheckIfInterrupted(intermediatePage); - ServiceIdentifier serviceIdentifier = new ServiceIdentifier(provider.getName(), phoneNumber); - ServiceUpdateDetails service = new ServiceUpdateDetails(serviceIdentifier); - service.setProperty(USAGE_URL, usageURL); + if (resp.getStatusLine().getStatusCode() != HttpStatus.SC_OK) + throw new IOException("Expected OK Response"); + + String intermediateDoc = EntityUtils.toString(resp.getEntity()); - result.add(service); + Pattern urlPattern = Pattern.compile("\\s*([0-9]+)\\s*"); + Matcher urlMatcher = urlPattern.matcher(intermediateDoc); + while(urlMatcher.find()) { + String realURL = prependHost(urlMatcher.group(1)); + String accountID = urlMatcher.group(2); + + ServiceIdentifier serviceIdentifier = new ServiceIdentifier(provider.getName(), accountID); + ServiceUpdateDetails service = new ServiceUpdateDetails(serviceIdentifier); + service.setProperty(INTERMEDIATE_URL, intermediateUsageURL); + service.setProperty(USAGE_URL, realURL); + + result.add(service); + } + } + if (result.size() == 0) { + throw new IOException("Didn't find any service identifiers"); } return result; } catch (IOException ex) { - throw new AccountUpdateException("Error logging in", ex); + throw new AccountUpdateException("Error fetching accounts", ex); } } - - Pattern usageSummaryPattern = Pattern.compile("([0-9]+(.[0-9]+)?) ([MKG])B"); - Pattern SMSCountPattern = Pattern.compile("([0-9]+) SMS"); - Pattern callCountPattern = Pattern.compile("([0-9]+) Calls?"); - Pattern amountPattern = Pattern.compile("\\$([0-9]+(\\.[0-9]{2,3})?)"); - - private Value parseValue(String amt) throws IOException { Matcher m = usageSummaryPattern.matcher(amt); if (m.matches()) { @@ -271,7 +279,8 @@ private Value parseValue(String amt) throws IOException { } private Value parseAmount(Matcher m, int index, boolean multiplForGST) { - BigDecimal bd = new BigDecimal(m.group(index)).multiply(new BigDecimal(100)); + String val = m.group(index).replace(",", ""); // Remove Commas, that appear in values larger than $1,000 + BigDecimal bd = new BigDecimal(val).multiply(new BigDecimal(100)); if (multiplForGST) { bd = bd.multiply(GST_MULTIPLIER); } @@ -280,26 +289,27 @@ private Value parseAmount(Matcher m, int index, boolean multiplForGST) { return amt; } + + public void fetchServiceDetails(Service service) throws AccountUpdateException, InterruptedException { String usageDoc = null; try { - HttpGet intermediatePage = new HttpGet((String)service.getProperty(USAGE_URL)); - HttpResponse resp = executeThenCheckIfInterrupted(intermediatePage); - if (resp.getStatusLine().getStatusCode() != HttpStatus.SC_OK) + // Optus requires the intermediate page to be loaded up first, that presumably sets variables in the user's session. + // We don't care about the contents. + String intermediateURL = service.getProperty(INTERMEDIATE_URL); + HttpGet intermediateUsagePage = new HttpGet(intermediateURL); + HttpResponse iresp = executeThenCheckIfInterrupted(intermediateUsagePage); + if (iresp.getStatusLine().getStatusCode() != HttpStatus.SC_OK) throw new IOException("Expected OK Response"); - - String intermediateDoc = EntityUtils.toString(resp.getEntity()); + iresp.getEntity().consumeContent(); + - Pattern urlPattern = Pattern.compile("\\s*" + service.getIdentifier() + "\\s*"); - Matcher urlMatcher = urlPattern.matcher(intermediateDoc); - if (!urlMatcher.find()) - throw new IOException("Couldn't find url for real usage"); - String realURL = prependHost(urlMatcher.group(1)); + String realURL = service.getProperty(USAGE_URL); HttpGet usagePage = new HttpGet(realURL); - resp = executeThenCheckIfInterrupted(usagePage); + HttpResponse resp = executeThenCheckIfInterrupted(usagePage); if (resp.getStatusLine().getStatusCode() != HttpStatus.SC_OK) throw new IOException("Expected OK Response"); @@ -336,16 +346,25 @@ public void fetchServiceDetails(Service service) throws AccountUpdateException, Pattern costPattern = Pattern.compile("\\$([0-9]+)(.[0-9]{2})?"); Matcher costMatcher = costPattern.matcher(planName); if (!costMatcher.find()) { - throw new IOException("Couldn't find plan cost in plan name"); + // As a fall back, pick up any number in the plan at all.... + costPattern = Pattern.compile("([0-9]+)(.[0-9]{2})?"); + costMatcher = costPattern.matcher(planName); + + if (!costMatcher.find()) { + throw new IOException("Couldn't find plan cost in plan name"); + } } plan.setCost(parseAmount(costMatcher, 1, false)); Pattern capSizePattern = Pattern.compile("\\$([0-9]+(.[0-9]{2})?)\\s+\\(\\$([0-9]+(.[0-9]{2})?) ex GST\\) of cap"); Matcher capSizeMatcher = capSizePattern.matcher(planExt); - if (!capSizeMatcher.find()) - throw new IOException("Couldn't find cap size"); - service.getMetricGroup(CAP_GROUP).setAllocation(parseAmount(capSizeMatcher, 1, false)); + if (capSizeMatcher.find()) { + service.getMetricGroup(CAP_GROUP).setAllocation(parseAmount(capSizeMatcher, 1, false)); + } else { + Log.w(NodeUsage.TAG, "Couldn't find cap size. Display could be screwed up by this."); + service.getMetricGroup(CAP_GROUP).setAllocation(new Value(0, Unit.CENT)); + } // See if there is included data in the plan @@ -353,53 +372,68 @@ public void fetchServiceDetails(Service service) throws AccountUpdateException, Matcher includedDataMatcher = includedDataPattern.matcher(planExt); if (includedDataMatcher.find()) { service.getMetricGroup(DATA_PACK_GROUP).setAllocation(parseIncludedData(includedDataMatcher)); - } Pattern boltOnPattern = Pattern.compile("Bolt-on:\\s+\\s*\\s*"); Matcher boltOnMatcher = boltOnPattern.matcher(usageDoc); - if (!boltOnMatcher.find(planMatcher.end())) - throw new IOException("Expected to find Bolt Ons"); - - int endOfTable = usageDoc.indexOf("
        ", boltOnMatcher.end()+1); - Pattern p2 = Pattern.compile("\\s*\\s*([^<]*)\\s*"); - Matcher m2 = p2.matcher(usageDoc); - m2.region(boltOnMatcher.end(), endOfTable); - - Pattern dataPattern = Pattern.compile("([0-9]+)([MG])B of included data"); - Pattern dataPattern2 = Pattern.compile("MobileInternet[a-zA-Z0-9]+([0-9]+)([MG])B"); - while (m2.find()) { - String extra = m2.group(1); - - plan.addPlanExtra(extra); - Matcher m = dataPattern.matcher(extra); - if (m.matches()) { - service.getMetricGroup(DATA_PACK_GROUP).setAllocation(parseIncludedData(m)); - } - m = dataPattern2.matcher(extra); - if (m.matches()) { - service.getMetricGroup(DATA_PACK_GROUP).setAllocation(parseIncludedData(m)); + if (boltOnMatcher.find(planMatcher.end())) { + int endOfTable = usageDoc.indexOf("", boltOnMatcher.end()+1); + Pattern p2 = Pattern.compile("\\s*\\s*([^<]*)\\s*"); + Matcher m2 = p2.matcher(usageDoc); + m2.region(boltOnMatcher.end(), endOfTable); + + while (m2.find()) { + String extra = m2.group(1); + + plan.addPlanExtra(extra); + + + for (Pattern p: boltOnDataPatterns) { + Matcher m = p.matcher(extra); + if (m.matches()) { + Log.d(NodeUsage.TAG, "Found bolt on data pack using pattern " + p); + service.getMetricGroup(DATA_PACK_GROUP).setAllocation(parseIncludedData(m)); + } + } } } - service.setPlan(plan); - Pattern monthStartPattern = Pattern.compile("\\s*Date From:\\s+\\s*\\s*"); + Pattern monthStartPattern = Pattern.compile("\\s*Date From:\\s+\\s*\\s*"); Matcher monthStartMatcher = monthStartPattern.matcher(usageDoc); if (!monthStartMatcher.find(planMatcher.end())) throw new IOException("Couldn't find Start Date"); - String startDateStr = cleanupSpaces(monthStartMatcher.group(1)); - Date startDate = DateTools.ensureMidnight(rolloverFormatter.parse(startDateStr), melbourneTZ); - Calendar cal = Calendar.getInstance(); - cal.setTime(startDate); - cal.add(Calendar.MONTH,1); - plan.setNextRollover(cal.getTime()); + if (monthStartMatcher.group(1) != null) { + String startDateStr = cleanupSpaces(monthStartMatcher.group(1)); + Log.d(NodeUsage.TAG, "Start date is " + startDateStr); + Date startDate = DateTools.ensureMidnight(rolloverFormatter.parse(startDateStr), melbourneTZ); + Calendar cal = Calendar.getInstance(); + cal.setTime(startDate); + cal.add(Calendar.MONTH,1); + + plan.setNextRollover(cal.getTime()); + } else { + // Its a new month, and they haven't told us the new rollover dates yet. We kind of have to guess. + // TODO If this is the first time a person has logged in, it will still shit itself.... + Date rollover = service.getPlan().getNextRollover(); + Log.d(NodeUsage.TAG, "Old rollover is " + rollover); + + while (rollover.before(new Date())) { + Calendar cal = Calendar.getInstance(); + cal.setTime(rollover); + cal.add(Calendar.MONTH, 1); + + rollover = cal.getTime(); + } + plan.setNextRollover(rollover); + } + service.setPlan(plan); int start = usageDoc.indexOf("", monthStartMatcher.end()); @@ -442,7 +476,7 @@ public void fetchServiceDetails(Service service) throws AccountUpdateException, } - Pattern totalPattern = Pattern.compile(""); + Pattern totalPattern = Pattern.compile(""); Matcher totalMatcher = totalPattern.matcher(usageDoc); totalMatcher.region(start, end); if (!totalMatcher.find()) @@ -522,7 +556,6 @@ public void fetchServiceDetails(Service service) throws AccountUpdateException, } catch (ParseException ex) { throw new AccountUpdateException("Error logging in", ex); } catch (IOException ex) { - Log.e(NodeUsage.TAG, "Error parsing results, doc is " + usageDoc); throw new AccountUpdateException("Error Parsing Results", ex); } } diff --git a/src/com/eightbitcloud/internode/provider/ProviderFetcher.java b/src/com/eightbitcloud/internode/provider/ProviderFetcher.java index 616658e..af9434b 100644 --- a/src/com/eightbitcloud/internode/provider/ProviderFetcher.java +++ b/src/com/eightbitcloud/internode/provider/ProviderFetcher.java @@ -8,7 +8,9 @@ public interface ProviderFetcher { List fetchAccountUpdates(Account account) throws AccountUpdateException, InterruptedException, WrongPasswordException; void fetchServiceDetails(Service service) throws AccountUpdateException, InterruptedException, WrongPasswordException; - void testUsernameAndPassword(Account account) throws AccountUpdateException, WrongPasswordException; + void testUsernameAndPassword(Account account) throws AccountUpdateException, WrongPasswordException, InterruptedException; void setLogTraffic(boolean val); + + void cleanup(); } diff --git a/src/com/eightbitcloud/internode/provider/VodafoneMBBFetcher.java b/src/com/eightbitcloud/internode/provider/VodafoneMBBFetcher.java index 503ae51..edce6b8 100644 --- a/src/com/eightbitcloud/internode/provider/VodafoneMBBFetcher.java +++ b/src/com/eightbitcloud/internode/provider/VodafoneMBBFetcher.java @@ -20,12 +20,15 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.client.params.CookiePolicy; import org.apache.http.client.params.HttpClientParams; -import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.apache.http.params.BasicHttpParams; -import org.apache.http.params.HttpProtocolParams; +import org.apache.http.params.HttpParams; import org.apache.http.util.EntityUtils; +import android.content.Context; +import android.util.Log; + +import com.eightbitcloud.internode.NodeUsage; import com.eightbitcloud.internode.UsageGraphType; import com.eightbitcloud.internode.data.Account; import com.eightbitcloud.internode.data.CounterStyle; @@ -52,22 +55,17 @@ public class VodafoneMBBFetcher extends AbstractFetcher { public TimeZone melbourneTZ; - public VodafoneMBBFetcher(Provider provider) { - super(provider); + public VodafoneMBBFetcher(Provider provider, Context ctx) { + super(provider, ctx); } @Override - public DefaultHttpClient createHttpClient() { - BasicHttpParams params = new BasicHttpParams(); - params.setParameter(HttpProtocolParams.USER_AGENT, "NodeDroid/2.02 (Android Usage Meter )"); + protected HttpParams createHttpParams() { + BasicHttpParams params = (BasicHttpParams) super.createHttpParams(); HttpClientParams.setCookiePolicy(params, CookiePolicy.RFC_2109); - - DefaultHttpClient client = new DefaultHttpClient(params); - return client; + return params; } - - private String login(Account account) throws AccountUpdateException, InterruptedException { try { HttpGet loginPage = new HttpGet("https://secure.broadband.vodafone.com.au/CRMVOD/login"); @@ -210,7 +208,7 @@ public void fetchServiceDetails(Service service) throws AccountUpdateException, try { MetricGroup dataGroup = new MetricGroup(service, DATA_GROUP, Unit.BYTE, CounterStyle.SIMPLE); - dataGroup.setGraphTypes(UsageGraphType.MONTHLY_USAGE); + dataGroup.setGraphTypes(UsageGraphType.ALL_USAGE); dataGroup.setStyle(CounterStyle.SIMPLE); MeasuredValue dataMval = new MeasuredValue(Unit.BYTE); @@ -221,7 +219,7 @@ public void fetchServiceDetails(Service service) throws AccountUpdateException, MetricGroup smsGroup = new MetricGroup(service, SMS_GROUP, Unit.COUNT, CounterStyle.SIMPLE); - smsGroup.setGraphTypes(UsageGraphType.MONTHLY_USAGE); + smsGroup.setGraphTypes(UsageGraphType.ALL_USAGE); smsGroup.setStyle(CounterStyle.SIMPLE); MeasuredValue smsMval = new MeasuredValue(Unit.COUNT); smsMval.setName(USAGE); @@ -253,7 +251,7 @@ public void fetchServiceDetails(Service service) throws AccountUpdateException, String usageStr = EntityUtils.toString(resp.getEntity()); - Pattern valueMatcher = Pattern.compile("([^<]*)"); + Pattern valueMatcher = Pattern.compile("([^<]*)"); Matcher m = valueMatcher.matcher(usageStr); String[] row; while ((row = getValues(7, m, 1)) != null) { @@ -277,6 +275,8 @@ public void fetchServiceDetails(Service service) throws AccountUpdateException, r.setCost(cost); r.setDescription(destination); + Log.d(NodeUsage.TAG, "Found usage record " + r); + if (type.equals("Data/GPRS")) { dataMval.addUsageRecord(r); } else if (type.equals("SMS")) { diff --git a/src/com/eightbitcloud/internode/util/DateTools.java b/src/com/eightbitcloud/internode/util/DateTools.java index 8f29065..0e10246 100644 --- a/src/com/eightbitcloud/internode/util/DateTools.java +++ b/src/com/eightbitcloud/internode/util/DateTools.java @@ -32,7 +32,9 @@ public class DateTools { } - + public static Date ensureMidnight(Date d) { + return ensureMidnight(d, TimeZone.getDefault()); + } // Make sure the Time is set to Midnight, Adelaide Time public static Date ensureMidnight(Date d, TimeZone tz) { @@ -46,6 +48,7 @@ public static Date ensureMidnight(Date d, TimeZone tz) { return c.getTime(); } + public static Date parseAdelaideDate(String dt) throws ParseException { Date parsed = DATE_PARSER_FORMAT.parse(dt); Date midnighted = ensureMidnight(parsed, adelaideTZ);
        ([0-9$\\.]+)
        ([0-9$\\.]+)?