diff --git a/src/org/mozilla/mozstumbler/client/MainActivity.java b/src/org/mozilla/mozstumbler/client/MainActivity.java index 5af9d6d39..42bacaa02 100644 --- a/src/org/mozilla/mozstumbler/client/MainActivity.java +++ b/src/org/mozilla/mozstumbler/client/MainActivity.java @@ -282,7 +282,7 @@ public void showUploadStats() { formatTextView(R.id.reports_sent, R.string.reports_sent, Integer.parseInt(value)); } catch (IOException ex) { - Log.e(LOGTAG, "Exception in showUploadStats():" + ex.toString()); + Log.e(LOGTAG, "Exception in showUploadStats()", ex); } } diff --git a/src/org/mozilla/mozstumbler/client/UploadReportsDialog.java b/src/org/mozilla/mozstumbler/client/UploadReportsDialog.java index bbf88db5d..fd9035e2e 100644 --- a/src/org/mozilla/mozstumbler/client/UploadReportsDialog.java +++ b/src/org/mozilla/mozstumbler/client/UploadReportsDialog.java @@ -163,7 +163,7 @@ void updateSyncedStats() { mTotalDataSentView.setText(String.valueOf(kilobytes)); } catch (IOException ex) { - Log.e(LOGTAG, "Exception in updateSyncedStats():" + ex.toString()); + Log.e(LOGTAG, "Exception in updateSyncedStats()", ex); } } diff --git a/src/org/mozilla/mozstumbler/service/StumblerService.java b/src/org/mozilla/mozstumbler/service/StumblerService.java index be82fdcf9..d81212c2b 100644 --- a/src/org/mozilla/mozstumbler/service/StumblerService.java +++ b/src/org/mozilla/mozstumbler/service/StumblerService.java @@ -23,7 +23,7 @@ import java.io.IOException; public final class StumblerService extends PersistentIntentService - implements DataStorageManager.DatabaseIsEmptyTracker { + implements DataStorageManager.StorageIsEmptyTracker { private static final String LOGTAG = StumblerService.class.getName(); public static final String ACTION_BASE = AppGlobals.ACTION_NAMESPACE; public static final String ACTION_START_PASSIVE = ACTION_BASE + ".START_PASSIVE"; @@ -161,7 +161,7 @@ public void onDestroy() { AppGlobals.dataStorageManager.saveCurrentReportsToDisk(); } catch (IOException ex) { AppGlobals.guiLogInfo(ex.toString()); - Log.e(LOGTAG, "Exception in onDestroy saving reports:" + ex.toString()); + Log.e(LOGTAG, "Exception in onDestroy saving reports", ex); } } mReporter.shutdown(); @@ -221,12 +221,11 @@ public void onRebind(Intent intent) { if (AppGlobals.isDebug)Log.d(LOGTAG,"onRebind"); } - public void databaseIsEmpty(boolean isEmpty) { + public void storageIsEmpty(boolean isEmpty) { if (isEmpty) { UploadAlarmReceiver.cancelAlarm(this); } else { UploadAlarmReceiver.scheduleAlarm(this); } } - -} +} \ No newline at end of file diff --git a/src/org/mozilla/mozstumbler/service/datahandling/DataStorageManager.java b/src/org/mozilla/mozstumbler/service/datahandling/DataStorageManager.java index fd6a29ace..8cd4cb6f8 100644 --- a/src/org/mozilla/mozstumbler/service/datahandling/DataStorageManager.java +++ b/src/org/mozilla/mozstumbler/service/datahandling/DataStorageManager.java @@ -8,11 +8,8 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.util.Log; - -import org.mozilla.mozstumbler.R; import org.mozilla.mozstumbler.service.AppGlobals; import org.mozilla.mozstumbler.service.utils.Zipper; - import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -22,13 +19,14 @@ import java.util.HashMap; import java.util.Map; import java.util.Properties; -import java.util.ResourceBundle; +import java.util.Timer; +import java.util.TimerTask; -/** Stores reports in memory (mCurrentReports) until MAX_REPORTS_IN_MEMORY, +/* Stores reports in memory (mCurrentReports) until MAX_REPORTS_IN_MEMORY, * then writes them to disk as a .gz file. The name of the file has * the time written, the # of reports, and the # of cells and wifis. * - * Each .gz file is typically 1-5KB. + * Each .gz file is typically 1-5KB. File name example: reports-t1406863343313-r4-w25-c7.gz * * The sync stats are written as a key-value pair file (not zipped). * @@ -49,13 +47,13 @@ public class DataStorageManager { private long mMaxBytesDiskStorage = 1024 * 1024 * 3; // 3 megabytes max by default private int mMaxWeeksStored = 2; private ReportBatchBuilder mCurrentReports = new ReportBatchBuilder(); - private int mTotalReportCount; private File mReportsDir; private File mStatsFile; private ReportBatch mCurrentReportsSendBuffer; private ReportBatchIterator mReportBatchIterator; - private DatabaseIsEmptyTracker mTracker; + private StorageIsEmptyTracker mTracker; private ReportFileList mFileList; + private Timer mFlushMemoryBuffersToDiskTimer; final static String SEP_REPORT_COUNT = "-r"; final static String SEP_WIFI_COUNT = "-w"; @@ -71,7 +69,7 @@ public static class QueuedCounts { public long bytes; } - /** Some data is calculated on-demand, don't abuse this function */ + /* Some data is calculated on-demand, don't abuse this function */ public QueuedCounts getQueuedCounts() { QueuedCounts q = new QueuedCounts(); q.reportCount = mFileList.reportCount + mCurrentReports.reports.size(); @@ -117,8 +115,9 @@ public ReportFileList(ReportFileList other) { void update(File directory) { files = directory.listFiles(); - if (files == null) + if (files == null) { return; + } filesOnDiskBytes = reportCount = wifiCount = cellCount = 0; for (File f : files) { @@ -154,22 +153,24 @@ public ReportBatchIterator(ReportFileList list) { public final ReportFileList fileList; } - public interface DatabaseIsEmptyTracker { - public void databaseIsEmpty(boolean isEmpty); + public interface StorageIsEmptyTracker { + public void storageIsEmpty(boolean isEmpty); } - public Map getOldDbStats(Context context) { + private Map getOldDbStats(Context context) { final File dbFile = new File(context.getFilesDir().getParent() + "/databases/" + "stumbler.db"); - if (!dbFile.exists()) + if (!dbFile.exists()) { return null; + } SQLiteDatabase db = SQLiteDatabase.openDatabase(dbFile.toString(), null, 0); Cursor cursor = null; try { cursor = db.rawQuery("select * from stats", null); - if (cursor == null) + if (cursor == null) { return null; + } cursor.moveToFirst(); Map kv = new HashMap(); @@ -182,14 +183,15 @@ public Map getOldDbStats(Context context) { assert(kv.size() == 4); return kv; } finally { - if (cursor != null) + if (cursor != null) { cursor.close(); + } db.close(); dbFile.delete(); } } - String getStorageDir(Context c) { + private String getStorageDir(Context c) { File dir = null; if (AppGlobals.isDebug) { // in debug, put files in public location @@ -209,7 +211,7 @@ String getStorageDir(Context c) { return dir.getPath(); } - public DataStorageManager(Context c, DatabaseIsEmptyTracker tracker) { + public DataStorageManager(Context c, StorageIsEmptyTracker tracker) { mTracker = tracker; String baseDir = getStorageDir(c); mStatsFile = new File(baseDir, "upload_stats.ini"); @@ -230,7 +232,7 @@ public DataStorageManager(Context c, DatabaseIsEmptyTracker tracker) { try { writeSyncStats(last_upload_time, 0, observations_sent, wifis_sent, cells_sent); } catch (IOException ex) { - Log.e(LOG_TAG, "Exception in DataStorageManager upgrading db:" + ex.toString()); + Log.e(LOG_TAG, "Exception in DataStorageManager upgrading db:", ex); } } } @@ -247,7 +249,7 @@ public int getMaxWeeksStored() { return mMaxWeeksStored; } - public static byte[] readFile(File file) throws IOException { + private static byte[] readFile(File file) throws IOException { RandomAccessFile f = new RandomAccessFile(file, "r"); try { byte[] data = new byte[(int)f.length()]; @@ -258,11 +260,11 @@ public static byte[] readFile(File file) throws IOException { } } - public boolean isDirEmpty() { + public synchronized boolean isDirEmpty() { return (mFileList.files == null || mFileList.files.length < 1); } - /** Pass filename returned from dataToSend() */ + /* Pass filename returned from dataToSend() */ public synchronized boolean delete(String filename) { if (filename == MEMORY_BUFFER_NAME) { mCurrentReportsSendBuffer = null; @@ -275,20 +277,22 @@ public synchronized boolean delete(String filename) { return ok; } - static long getLongFromFilename(String name, String separator) { + private static long getLongFromFilename(String name, String separator) { int s = name.indexOf(separator) + separator.length(); int e = name.indexOf('-', s); - if (e < 0) + if (e < 0) { e = name.indexOf('.', s); + } return Long.parseLong(name.substring(s, e)); } - /** return name of file used, or memory buffer sentinel value. - * The return value is used to delete the file/buffer later.*/ + /* return name of file used, or memory buffer sentinel value. + * The return value is used to delete the file/buffer later. */ public synchronized ReportBatch getFirstBatch() throws IOException { boolean dirEmpty = isDirEmpty(); - if (dirEmpty && mCurrentReports.reports.size() < 1) + if (dirEmpty && mCurrentReports.reports.size() < 1) { return null; + } mReportBatchIterator = new ReportBatchIterator(mFileList); @@ -307,14 +311,15 @@ public synchronized ReportBatch getFirstBatch() throws IOException { } } - void clearCurrentReports() { + private void clearCurrentReports() { mCurrentReports.reports.clear(); mCurrentReports.wifiCount = mCurrentReports.cellCount = 0; } public synchronized ReportBatch getNextBatch() throws IOException { - if (mReportBatchIterator == null) + if (mReportBatchIterator == null) { return null; + } mReportBatchIterator.currentIndex++; if (mReportBatchIterator.currentIndex < 0 || @@ -332,7 +337,7 @@ public synchronized ReportBatch getNextBatch() throws IOException { return result; } - File createFile(int reportCount, int wifiCount, int cellCount) { + private File createFile(int reportCount, int wifiCount, int cellCount) { long time = System.currentTimeMillis(); String name = FILENAME_PREFIX + SEP_TIME_MS + time + @@ -343,22 +348,25 @@ File createFile(int reportCount, int wifiCount, int cellCount) { return file; } - public long getOldestBatchTimeMs() { - if (isDirEmpty()) + public synchronized long getOldestBatchTimeMs() { + if (isDirEmpty()) { return 0; + } long oldest = Long.MAX_VALUE; for (File f : mFileList.files) { long t = getLongFromFilename(f.getName(), SEP_TIME_MS); - if (t < oldest) + if (t < oldest) { oldest = t; + } } return oldest; } - public void saveCurrentReportsSendBufferToDisk() throws IOException { - if (mCurrentReportsSendBuffer == null) + public synchronized void saveCurrentReportsSendBufferToDisk() throws IOException { + if (mCurrentReportsSendBuffer == null) { return; + } saveToDisk(mCurrentReportsSendBuffer.data, mCurrentReportsSendBuffer.reportCount, @@ -367,7 +375,7 @@ public void saveCurrentReportsSendBufferToDisk() throws IOException { mCurrentReportsSendBuffer = null; } - public synchronized void saveToDisk(byte[] bytes, int reportCount, int wifiCount, int cellCount) + private void saveToDisk(byte[] bytes, int reportCount, int wifiCount, int cellCount) throws IOException { if (mFileList.filesOnDiskBytes > mMaxBytesDiskStorage) return; @@ -377,13 +385,14 @@ public synchronized void saveToDisk(byte[] bytes, int reportCount, int wifiCount fos = new FileOutputStream(createFile(reportCount, wifiCount, cellCount)); fos.write(bytes); } finally { - if (fos != null) + if (fos != null) { fos.close(); + } } mFileList.update(mReportsDir); } - String finalizeReports(ArrayList reports) { + private String finalizeReports(ArrayList reports) { final String kPrefix = "{\"items\":["; final String kSuffix = "]}"; @@ -409,23 +418,42 @@ public synchronized void saveCurrentReportsToDisk() throws IOException { } public synchronized void insert(String report, int wifiCount, int cellCount) throws IOException { - notifyDbIsEmpty(false); + notifyStorageIsEmpty(false); + + if (mFlushMemoryBuffersToDiskTimer != null) { + mFlushMemoryBuffersToDiskTimer.cancel(); + mFlushMemoryBuffersToDiskTimer = null; + } mCurrentReports.reports.add(report); mCurrentReports.wifiCount = wifiCount; mCurrentReports.cellCount = cellCount; - mTotalReportCount++; - if (mCurrentReports.reports.size() >= MAX_REPORTS_IN_MEMORY) { // save to disk saveCurrentReportsToDisk(); + } else { + // Schedule a timer to flush to disk after a few mins. + // If collection stops and wifi not available for uploading, the memory buffer is flushed to disk. + final int kMillis = 1000 * 60 * 3; + mFlushMemoryBuffersToDiskTimer = new Timer(); + mFlushMemoryBuffersToDiskTimer.schedule(new TimerTask() { + @Override + public void run() { + try { + saveCurrentReportsToDisk(); + } catch (IOException ex) { + Log.e(LOG_TAG, "mFlushMemoryBuffersToDiskTimer exception", ex); + } + } + }, kMillis); } } - public Properties readSyncStats() throws IOException { - if (!mStatsFile.exists()) + public synchronized Properties readSyncStats() throws IOException { + if (!mStatsFile.exists()) { return new Properties(); + } FileInputStream input = new FileInputStream(mStatsFile); try { @@ -438,9 +466,10 @@ public Properties readSyncStats() throws IOException { } } - public void incrementSyncStats(long bytesSent, long reports, long cells, long wifis) throws IOException { - if (reports + cells + wifis < 1) + public synchronized void incrementSyncStats(long bytesSent, long reports, long cells, long wifis) throws IOException { + if (reports + cells + wifis < 1) { return; + } Properties properties = readSyncStats(); long time = System.currentTimeMillis(); @@ -451,21 +480,22 @@ public void incrementSyncStats(long bytesSent, long reports, long cells, long wi Long.parseLong(properties.getProperty(DataStorageContract.Stats.KEY_WIFIS_SENT, "0")) + wifis); } - public void writeSyncStats(long time, long bytesSent, long totalObs, long totalCells, long totalWifis) throws IOException { + private void writeSyncStats(long time, long bytesSent, long totalObs, long totalCells, long totalWifis) throws IOException { FileOutputStream out = null; try { out = new FileOutputStream(mStatsFile); Properties props = new Properties(); - props.setProperty(DataStorageContract.Stats.KEY_LAST_UPLOAD_TIME, "" + time); - props.setProperty(DataStorageContract.Stats.KEY_BYTES_SENT, "" + bytesSent); - props.setProperty(DataStorageContract.Stats.KEY_OBSERVATIONS_SENT, "" + totalObs); - props.setProperty(DataStorageContract.Stats.KEY_CELLS_SENT, "" + totalCells); - props.setProperty(DataStorageContract.Stats.KEY_WIFIS_SENT, "" + totalWifis); - props.setProperty(DataStorageContract.Stats.KEY_VERSION, "" + DataStorageContract.Stats.VERSION_CODE); + props.setProperty(DataStorageContract.Stats.KEY_LAST_UPLOAD_TIME, String.valueOf(time)); + props.setProperty(DataStorageContract.Stats.KEY_BYTES_SENT, String.valueOf(bytesSent)); + props.setProperty(DataStorageContract.Stats.KEY_OBSERVATIONS_SENT, String.valueOf(totalObs)); + props.setProperty(DataStorageContract.Stats.KEY_CELLS_SENT, String.valueOf(totalCells)); + props.setProperty(DataStorageContract.Stats.KEY_WIFIS_SENT, String.valueOf(totalWifis)); + props.setProperty(DataStorageContract.Stats.KEY_VERSION, String.valueOf(DataStorageContract.Stats.VERSION_CODE)); props.store(out, null); } finally { - if (out != null) + if (out != null) { out.close(); + } } } @@ -476,8 +506,9 @@ public synchronized void deleteAll() { mFileList.update(mReportsDir); } - public void notifyDbIsEmpty(boolean isEmpty) { - if (mTracker != null) - mTracker.databaseIsEmpty(isEmpty); + private void notifyStorageIsEmpty(boolean isEmpty) { + if (mTracker != null) { + mTracker.storageIsEmpty(isEmpty); + } } } diff --git a/src/org/mozilla/mozstumbler/service/scanners/WifiScanner.java b/src/org/mozilla/mozstumbler/service/scanners/WifiScanner.java index 67690c0f5..d18c6990e 100644 --- a/src/org/mozilla/mozstumbler/service/scanners/WifiScanner.java +++ b/src/org/mozilla/mozstumbler/service/scanners/WifiScanner.java @@ -155,7 +155,7 @@ public void run() { stop(); // set mWifiScanTimer to null return; } - if (AppGlobals.isDebug) Log.d(LOGTAG, "WiFi Scanning Timer fired"); + if (AppGlobals.isDebug) Log.v(LOGTAG, "WiFi Scanning Timer fired"); getWifiManager().startScan(); } }, 0, WIFI_MIN_UPDATE_TIME); @@ -197,7 +197,7 @@ private void reportScanResults(ArrayList scanResults) { if (scanResults.isEmpty()) return; - if (AppGlobals.isDebug) Log.d(LOGTAG, scanResults.toString()); + if (AppGlobals.isDebug) Log.v(LOGTAG, scanResults.toString()); Intent i = new Intent(ACTION_WIFIS_SCANNED); i.putParcelableArrayListExtra(ACTION_WIFIS_SCANNED_ARG_RESULTS, scanResults); diff --git a/src/org/mozilla/mozstumbler/service/sync/UploadAlarmReceiver.java b/src/org/mozilla/mozstumbler/service/sync/UploadAlarmReceiver.java index 3cb1b3990..3775caade 100644 --- a/src/org/mozilla/mozstumbler/service/sync/UploadAlarmReceiver.java +++ b/src/org/mozilla/mozstumbler/service/sync/UploadAlarmReceiver.java @@ -19,9 +19,13 @@ public class UploadAlarmReceiver extends BroadcastReceiver { static final String LOG_TAG = UploadAlarmReceiver.class.getName(); - // Only if data is queued and device awake: every 5 min, check network availability and upload + // Only if data is queued and device awake: periodically check network availability and upload + // TODO Fennec will only use this as a secondary mechanism. The primary Fennec method + // notifying this class when a good time is to try upload. static final long INTERVAL_MS = 1000 * 60 * 5; + public static boolean sIsAlreadyScheduled; + public UploadAlarmReceiver() {} public static class UploadAlarmService extends IntentService { @@ -71,14 +75,21 @@ static PendingIntent createIntent(Context c) { } public static void cancelAlarm(Context c) { + Log.d(LOG_TAG, "cancelAlarm"); + // this is to stop scheduleAlarm from constantly rescheduling, not to guard cancellation. + sIsAlreadyScheduled = false; AlarmManager alarmManager = (AlarmManager)c.getSystemService(Context.ALARM_SERVICE); PendingIntent pi = createIntent(c); alarmManager.cancel(pi); } public static void scheduleAlarm(Context c) { - cancelAlarm(c); + if (sIsAlreadyScheduled) + return; + + Log.d(LOG_TAG, "schedule alarm (ms):" + INTERVAL_MS); + sIsAlreadyScheduled = true; AlarmManager alarmManager = (AlarmManager)c.getSystemService(Context.ALARM_SERVICE); PendingIntent pi = createIntent(c);