From d19d57e5df9ac0b423547f49ac80e16d538808d5 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sun, 25 Dec 2016 11:47:25 -0500 Subject: [PATCH 1/9] Use Storage Access Framework when importing files --- .../habits/list/ListHabitsScreen.java | 52 ++++++++++++++++++- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java index 3fb92e817..1238daada 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java @@ -20,6 +20,7 @@ package org.isoron.uhabits.activities.habits.list; import android.content.*; +import android.net.*; import android.support.annotation.*; import org.isoron.uhabits.*; @@ -31,24 +32,30 @@ import org.isoron.uhabits.commands.*; import org.isoron.uhabits.intents.*; import org.isoron.uhabits.io.*; import org.isoron.uhabits.models.*; +import org.isoron.uhabits.utils.*; import java.io.*; import javax.inject.*; +import static android.os.Build.VERSION.*; +import static android.os.Build.VERSION_CODES.*; + @ActivityScope public class ListHabitsScreen extends BaseScreen implements CommandRunner.Listener { - public static final int RESULT_BUG_REPORT = 4; + public static final int RESULT_IMPORT_DATA = 1; public static final int RESULT_EXPORT_CSV = 2; public static final int RESULT_EXPORT_DB = 3; + public static final int RESULT_BUG_REPORT = 4; + public static final int RESULT_REPAIR_DB = 5; - public static final int RESULT_IMPORT_DATA = 1; + public static final int REQUEST_OPEN_DOCUMENT = 6; @Nullable private ListHabitsController controller; @@ -128,6 +135,31 @@ public class ListHabitsScreen extends BaseScreen { if (controller == null) return; + if (requestCode == REQUEST_OPEN_DOCUMENT) + { + if(resultCode != BaseActivity.RESULT_OK) return; + try + { + // TODO: Make it async + // TODO: Remove temporary file at the end of operation + Uri uri = data.getData(); + ContentResolver cr = activity.getContentResolver(); + InputStream is = cr.openInputStream(uri); + + File cacheDir = activity.getCacheDir(); + File tempFile = File.createTempFile("import", "", cacheDir); + + FileUtils.copy(is, tempFile); + controller.onImportData(tempFile); + } + catch (IOException e) + { + showMessage(R.string.could_not_import); + e.printStackTrace(); + return; + } + } + switch (resultCode) { case RESULT_IMPORT_DATA: @@ -208,6 +240,21 @@ public class ListHabitsScreen extends BaseScreen } public void showImportScreen() + { + if (SDK_INT < KITKAT) + { + showImportScreenPreKitKat(); + return; + } + + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + + activity.startActivityForResult(intent, REQUEST_OPEN_DOCUMENT); + } + + public void showImportScreenPreKitKat() { File dir = dirFinder.findStorageDir(null); @@ -221,6 +268,7 @@ public class ListHabitsScreen extends BaseScreen if (controller != null) picker.setListener(file -> controller.onImportData(file)); + activity.showDialog(picker.getDialog()); } From 6adf8061d3d2bd1c5cff60bda30c938a453cd2b3 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sun, 25 Dec 2016 23:05:17 -0500 Subject: [PATCH 2/9] Use FileProvider instead of File URIs --- app/src/main/AndroidManifest.xml | 10 +++++++ .../isoron/uhabits/activities/BaseScreen.java | 10 +++++-- app/src/main/res/xml/file_paths.xml | 27 +++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 app/src/main/res/xml/file_paths.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ee57323d5..1bab9be06 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -223,5 +223,15 @@ + + + + diff --git a/app/src/main/java/org/isoron/uhabits/activities/BaseScreen.java b/app/src/main/java/org/isoron/uhabits/activities/BaseScreen.java index 1954c7c42..64621414c 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/BaseScreen.java +++ b/app/src/main/java/org/isoron/uhabits/activities/BaseScreen.java @@ -40,6 +40,7 @@ import java.io.*; import static android.os.Build.VERSION.*; import static android.os.Build.VERSION_CODES.*; +import static android.support.v4.content.FileProvider.*; /** * Base class for all screens in the application. @@ -50,6 +51,8 @@ import static android.os.Build.VERSION_CODES.*; */ public class BaseScreen { + public static final int REQUEST_CREATE_DOCUMENT = 1; + protected BaseActivity activity; @Nullable @@ -230,11 +233,14 @@ public class BaseScreen public void showSendFileScreen(@NonNull String archiveFilename) { + File file = new File(archiveFilename); + Uri fileUri = getUriForFile(activity, "org.isoron.uhabits", file); + Intent intent = new Intent(); intent.setAction(Intent.ACTION_SEND); intent.setType("application/zip"); - intent.putExtra(Intent.EXTRA_STREAM, - Uri.fromFile(new File(archiveFilename))); + intent.putExtra(Intent.EXTRA_STREAM, fileUri); + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); activity.startActivity(intent); } diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml new file mode 100644 index 000000000..d97286ddf --- /dev/null +++ b/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,27 @@ + + + + + + + + + + From 02c8810e463932e1a62cd670cc020b12c8d69d94 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sun, 25 Dec 2016 23:05:47 -0500 Subject: [PATCH 3/9] Bump targetSdkVersion to 25 --- app/build.gradle | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6bdb20d23..b6e28d599 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,13 +4,13 @@ apply plugin: 'me.tatarka.retrolambda' apply plugin: 'jacoco' android { - compileSdkVersion 23 - buildToolsVersion "23.0.3" + compileSdkVersion 25 + buildToolsVersion "25.0.2" defaultConfig { applicationId "org.isoron.uhabits" minSdkVersion 15 - targetSdkVersion 23 + targetSdkVersion 25 buildConfigField "Integer", "databaseVersion", "15" buildConfigField "String", "databaseFilename", "\"uhabits.db\"" @@ -53,7 +53,7 @@ dependencies { androidTestApt 'com.google.dagger:dagger-compiler:2.2' - androidTestCompile 'com.android.support:support-annotations:23.3.0' + androidTestCompile 'com.android.support:support-annotations:25.1.0' androidTestCompile 'com.android.support.test:rules:0.5' androidTestCompile 'com.android.support.test:runner:0.5' androidTestCompile 'com.google.auto.factory:auto-factory:1.0-beta3' @@ -64,10 +64,10 @@ dependencies { apt 'com.google.dagger:dagger-compiler:2.2' apt 'com.jakewharton:butterknife-compiler:8.0.1' - compile 'com.android.support:appcompat-v7:23.3.0' - compile 'com.android.support:design:23.3.0' - compile 'com.android.support:preference-v14:23.3.0' - compile 'com.android.support:support-v4:23.3.0' + compile 'com.android.support:appcompat-v7:25.1.0' + compile 'com.android.support:design:25.1.0' + compile 'com.android.support:preference-v14:25.1.0' + compile 'com.android.support:support-v4:25.1.0' compile 'com.getpebble:pebblekit:3.0.0' compile 'com.github.paolorotolo:appintro:3.4.0' compile 'com.google.auto.factory:auto-factory:1.0-beta3' From d81fdb41dcc1759a9ff328e40f6a2b581928a98a Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sun, 25 Dec 2016 23:38:07 -0500 Subject: [PATCH 4/9] Remove temp file after importing --- .../habits/list/ListHabitsController.java | 10 ++- .../habits/list/ListHabitsScreen.java | 65 +++++++++++-------- .../habits/list/ListHabitsScreenTest.java | 11 ++-- 3 files changed, 53 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java index af89c14ff..1d6462893 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java @@ -128,7 +128,8 @@ public class ListHabitsController taskRunner.execute(() -> habitList.reorder(from, to)); } - public void onImportData(@NonNull File file) + public void onImportData(@NonNull File file, + @NonNull OnFinishedListener finishedListener) { taskRunner.execute(importTaskFactory.create(file, result -> { switch (result) @@ -146,6 +147,8 @@ public class ListHabitsController screen.showMessage(R.string.could_not_import); break; } + + finishedListener.onFinish(); })); } @@ -208,4 +211,9 @@ public class ListHabitsController prefs.updateLastHint(-1, DateUtils.getStartOfToday()); screen.showIntroScreen(); } + + public interface OnFinishedListener + { + void onFinish(); + } } diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java index 1238daada..045505a90 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java @@ -19,6 +19,7 @@ package org.isoron.uhabits.activities.habits.list; +import android.app.*; import android.content.*; import android.net.*; import android.support.annotation.*; @@ -57,6 +58,8 @@ public class ListHabitsScreen extends BaseScreen public static final int REQUEST_OPEN_DOCUMENT = 6; + public static final int REQUEST_SETTINGS = 7; + @Nullable private ListHabitsController controller; @@ -133,32 +136,16 @@ public class ListHabitsScreen extends BaseScreen @Override public void onResult(int requestCode, int resultCode, Intent data) { - if (controller == null) return; - if (requestCode == REQUEST_OPEN_DOCUMENT) - { - if(resultCode != BaseActivity.RESULT_OK) return; - try - { - // TODO: Make it async - // TODO: Remove temporary file at the end of operation - Uri uri = data.getData(); - ContentResolver cr = activity.getContentResolver(); - InputStream is = cr.openInputStream(uri); - - File cacheDir = activity.getCacheDir(); - File tempFile = File.createTempFile("import", "", cacheDir); - - FileUtils.copy(is, tempFile); - controller.onImportData(tempFile); - } - catch (IOException e) - { - showMessage(R.string.could_not_import); - e.printStackTrace(); - return; - } - } + onOpenDocumentResult(resultCode, data); + + if (requestCode == REQUEST_SETTINGS) + onSettingsResult(resultCode); + } + + private void onSettingsResult(int resultCode) + { + if (controller == null) return; switch (resultCode) { @@ -184,6 +171,30 @@ public class ListHabitsScreen extends BaseScreen } } + private void onOpenDocumentResult(int resultCode, Intent data) + { + if (controller == null) return; + if (resultCode != Activity.RESULT_OK) return; + + try + { + Uri uri = data.getData(); + ContentResolver cr = activity.getContentResolver(); + InputStream is = cr.openInputStream(uri); + + File cacheDir = activity.getExternalCacheDir(); + File tempFile = File.createTempFile("import", "", cacheDir); + + FileUtils.copy(is, tempFile); + controller.onImportData(tempFile, () -> tempFile.delete()); + } + catch (IOException e) + { + showMessage(R.string.could_not_import); + e.printStackTrace(); + } + } + public void setController(@Nullable ListHabitsController controller) { this.controller = controller; @@ -267,7 +278,7 @@ public class ListHabitsScreen extends BaseScreen FilePickerDialog picker = filePickerDialogFactory.create(dir); if (controller != null) - picker.setListener(file -> controller.onImportData(file)); + picker.setListener(file -> controller.onImportData(file, () -> {})); activity.showDialog(picker.getDialog()); } @@ -281,7 +292,7 @@ public class ListHabitsScreen extends BaseScreen public void showSettingsScreen() { Intent intent = intentFactory.startSettingsActivity(activity); - activity.startActivityForResult(intent, 0); + activity.startActivityForResult(intent, REQUEST_SETTINGS); } public void toggleNightMode() diff --git a/app/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreenTest.java b/app/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreenTest.java index 5c4b413ae..da6c04587 100644 --- a/app/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreenTest.java +++ b/app/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreenTest.java @@ -38,10 +38,11 @@ import org.junit.runners.*; import java.io.*; +import static org.isoron.uhabits.activities.habits.list.ListHabitsScreen.*; import static org.mockito.Matchers.any; import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.eq; import static org.mockito.Mockito.*; +import static org.mockito.Mockito.eq; @RunWith(JUnit4.class) public class ListHabitsScreenTest extends BaseUnitTest @@ -124,28 +125,28 @@ public class ListHabitsScreenTest extends BaseUnitTest @Test public void testOnResult_bugReport() { - screen.onResult(0, ListHabitsScreen.RESULT_BUG_REPORT, null); + screen.onResult(REQUEST_SETTINGS, RESULT_BUG_REPORT, null); verify(controller).onSendBugReport(); } @Test public void testOnResult_exportCSV() { - screen.onResult(0, ListHabitsScreen.RESULT_EXPORT_CSV, null); + screen.onResult(REQUEST_SETTINGS, RESULT_EXPORT_CSV, null); verify(controller).onExportCSV(); } @Test public void testOnResult_exportDB() { - screen.onResult(0, ListHabitsScreen.RESULT_EXPORT_DB, null); + screen.onResult(REQUEST_SETTINGS, RESULT_EXPORT_DB, null); verify(controller).onExportDB(); } @Test public void testOnResult_importData() { - screen.onResult(0, ListHabitsScreen.RESULT_IMPORT_DATA, null); + screen.onResult(REQUEST_SETTINGS, RESULT_IMPORT_DATA, null); testShowImportScreen(); } From edd5f25529a1847108537b1df90aac33c3a8f2d6 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sun, 25 Dec 2016 23:56:34 -0500 Subject: [PATCH 5/9] Update circle.yml --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 243168196..c33a1c355 100644 --- a/circle.yml +++ b/circle.yml @@ -1,7 +1,7 @@ dependencies: pre: - echo y | android update sdk --no-ui --all --filter "tools" - - echo y | android update sdk --no-ui --all --filter "build-tools-23.0.3" + - echo y | android update sdk --no-ui --all --filter "build-tools-25.0.2" checkout: post: - git submodule sync From 0a8b763ece2532cc108349f92ea5655c876f1eb9 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Fri, 30 Dec 2016 12:58:37 -0500 Subject: [PATCH 6/9] Make TimePicker slightly smaller Fixes #219 --- app/src/main/res/values/pickers.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values/pickers.xml b/app/src/main/res/values/pickers.xml index 354c3ae89..c83a58066 100644 --- a/app/src/main/res/values/pickers.xml +++ b/app/src/main/res/values/pickers.xml @@ -41,12 +41,12 @@ 14sp 6dip 4dip - 96dip + 80dip 48dip 48dip 24dip - 270dip - 270dp + 250dip + 250dp 30dp 155dp 270dp From 09f1ae8765c557f4919f49f69ff0766608d2d5f4 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sat, 31 Dec 2016 12:30:10 -0500 Subject: [PATCH 7/9] Update circle.yml --- circle.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index c33a1c355..f0e7ffa46 100644 --- a/circle.yml +++ b/circle.yml @@ -1,7 +1,9 @@ dependencies: pre: - echo y | android update sdk --no-ui --all --filter "tools" + - echo y | android update sdk --no-ui --all --filter "android-25" - echo y | android update sdk --no-ui --all --filter "build-tools-25.0.2" + - echo y | android update sdk --no-ui --all --filter "extra-android-m2repository" checkout: post: - git submodule sync @@ -24,4 +26,4 @@ general: - poeditor machine: java: - version: oraclejdk8 \ No newline at end of file + version: oraclejdk8 From b3fe9c65d203109ee4daedb36318ce6888947965 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sat, 31 Dec 2016 18:24:26 -0500 Subject: [PATCH 8/9] Refresh data at midnight Fixes #221 --- .../habits/list/ListHabitsActivity.java | 7 ++ .../habits/list/ListHabitsComponent.java | 5 +- .../list/model/HabitCardListAdapter.java | 18 ++++- .../habits/list/views/HeaderView.java | 21 ++++- .../org/isoron/uhabits/utils/DateUtils.java | 5 ++ .../isoron/uhabits/utils/MidnightTimer.java | 77 +++++++++++++++++++ 6 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/org/isoron/uhabits/utils/MidnightTimer.java diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.java index d5529c2a1..deb1b3649 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.java @@ -25,6 +25,7 @@ import org.isoron.uhabits.*; import org.isoron.uhabits.activities.*; import org.isoron.uhabits.activities.habits.list.model.*; import org.isoron.uhabits.preferences.*; +import org.isoron.uhabits.utils.*; /** * Activity that allows the user to see and modify the list of habits. @@ -43,6 +44,8 @@ public class ListHabitsActivity extends BaseActivity private Preferences prefs; + private MidnightTimer midnightTimer; + public ListHabitsComponent getListHabitsComponent() { return component; @@ -77,6 +80,8 @@ public class ListHabitsActivity extends BaseActivity screen.setSelectionMenu(selectionMenu); rootView.setController(controller, selectionMenu); + midnightTimer = component.getMidnightTimer(); + setScreen(screen); controller.onStartup(); } @@ -84,6 +89,7 @@ public class ListHabitsActivity extends BaseActivity @Override protected void onPause() { + midnightTimer.onPause(); screen.onDettached(); adapter.cancelRefresh(); super.onPause(); @@ -95,6 +101,7 @@ public class ListHabitsActivity extends BaseActivity adapter.refresh(); screen.onAttached(); rootView.postInvalidate(); + midnightTimer.onResume(); if (prefs.getTheme() == ThemeSwitcher.THEME_DARK && prefs.isPureBlackEnabled() != pureBlack) diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsComponent.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsComponent.java index 658d6c573..554c22a3b 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsComponent.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsComponent.java @@ -23,13 +23,14 @@ import org.isoron.uhabits.*; import org.isoron.uhabits.activities.*; import org.isoron.uhabits.activities.habits.list.controllers.*; import org.isoron.uhabits.activities.habits.list.model.*; +import org.isoron.uhabits.utils.*; import dagger.*; @ActivityScope @Component(modules = { ActivityModule.class }, dependencies = { AppComponent.class }) -public interface ListHabitsComponent extends ActivityComponent +public interface ListHabitsComponent { CheckmarkButtonControllerFactory getCheckmarkButtonControllerFactory(); @@ -44,4 +45,6 @@ public interface ListHabitsComponent extends ActivityComponent ListHabitsScreen getScreen(); ListHabitsSelectionMenu getSelectionMenu(); + + MidnightTimer getMidnightTimer(); } diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/model/HabitCardListAdapter.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/model/HabitCardListAdapter.java index 7b91c3df8..55656b542 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/model/HabitCardListAdapter.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/model/HabitCardListAdapter.java @@ -28,6 +28,7 @@ import org.isoron.uhabits.activities.habits.list.*; import org.isoron.uhabits.activities.habits.list.views.*; import org.isoron.uhabits.models.*; import org.isoron.uhabits.preferences.*; +import org.isoron.uhabits.utils.*; import java.util.*; @@ -42,7 +43,7 @@ import javax.inject.*; @ActivityScope public class HabitCardListAdapter extends RecyclerView.Adapter - implements HabitCardListCache.Listener + implements HabitCardListCache.Listener, MidnightTimer.MidnightListener { @NonNull private ModelObservable observable; @@ -59,15 +60,20 @@ public class HabitCardListAdapter @NonNull private Preferences preferences; + private final MidnightTimer midnightTimer; + @Inject public HabitCardListAdapter(@NonNull HabitCardListCache cache, - @NonNull Preferences preferences) + @NonNull Preferences preferences, + @NonNull MidnightTimer midnightTimer) { this.preferences = preferences; this.selected = new LinkedList<>(); this.observable = new ModelObservable(); this.cache = cache; + this.midnightTimer = midnightTimer; + cache.setListener(this); cache.setCheckmarkCount(ListHabitsRootView.MAX_CHECKMARK_COUNT); cache.setOrder(preferences.getDefaultOrder()); @@ -75,6 +81,12 @@ public class HabitCardListAdapter setHasStableIds(true); } + @Override + public void atMidnight() + { + cache.refreshAllHabits(); + } + public void cancelRefresh() { cache.cancelTasks(); @@ -148,6 +160,7 @@ public class HabitCardListAdapter public void onAttached() { cache.onAttached(); + midnightTimer.addListener(this); } @Override @@ -180,6 +193,7 @@ public class HabitCardListAdapter public void onDetached() { cache.onDetached(); + midnightTimer.removeListener(this); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HeaderView.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HeaderView.java index 70ed6ec91..8f9d7c4d0 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HeaderView.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HeaderView.java @@ -26,12 +26,14 @@ import android.view.*; import android.widget.*; import org.isoron.uhabits.*; +import org.isoron.uhabits.activities.habits.list.*; import org.isoron.uhabits.preferences.*; import org.isoron.uhabits.utils.*; import java.util.*; -public class HeaderView extends LinearLayout implements Preferences.Listener +public class HeaderView extends LinearLayout + implements Preferences.Listener, MidnightTimer.MidnightListener { private final Context context; @@ -40,6 +42,9 @@ public class HeaderView extends LinearLayout implements Preferences.Listener @Nullable private Preferences prefs; + @Nullable + private MidnightTimer midnightTimer; + public HeaderView(Context context, AttributeSet attrs) { super(context, attrs); @@ -56,6 +61,18 @@ public class HeaderView extends LinearLayout implements Preferences.Listener HabitsApplication app = (HabitsApplication) appContext; prefs = app.getComponent().getPreferences(); } + + if (context instanceof ListHabitsActivity) + { + ListHabitsActivity activity = (ListHabitsActivity) context; + midnightTimer = activity.getListHabitsComponent().getMidnightTimer(); + } + } + + @Override + public void atMidnight() + { + post(() -> createButtons()); } @Override @@ -75,11 +92,13 @@ public class HeaderView extends LinearLayout implements Preferences.Listener { super.onAttachedToWindow(); if (prefs != null) prefs.addListener(this); + if (midnightTimer != null) midnightTimer.addListener(this); } @Override protected void onDetachedFromWindow() { + if (midnightTimer != null) midnightTimer.removeListener(this); if (prefs != null) prefs.removeListener(this); super.onDetachedFromWindow(); } diff --git a/app/src/main/java/org/isoron/uhabits/utils/DateUtils.java b/app/src/main/java/org/isoron/uhabits/utils/DateUtils.java index c85f90df2..cce452145 100644 --- a/app/src/main/java/org/isoron/uhabits/utils/DateUtils.java +++ b/app/src/main/java/org/isoron/uhabits/utils/DateUtils.java @@ -187,6 +187,11 @@ public abstract class DateUtils return getStartOfDay(DateUtils.getLocalTime()); } + public static long millisecondsUntilTomorrow() + { + return getStartOfToday() + millisecondsInOneDay - getLocalTime(); + } + public static GregorianCalendar getStartOfTodayCalendar() { return getCalendar(getStartOfToday()); diff --git a/app/src/main/java/org/isoron/uhabits/utils/MidnightTimer.java b/app/src/main/java/org/isoron/uhabits/utils/MidnightTimer.java new file mode 100644 index 000000000..30021ae71 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/utils/MidnightTimer.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2016 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Loop Habit Tracker is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +package org.isoron.uhabits.utils; + +import org.isoron.uhabits.activities.*; + +import java.util.*; +import java.util.concurrent.*; + +import javax.inject.*; + +/** + * A class that emits events when a new day starts. + */ +@ActivityScope +public class MidnightTimer +{ + private final List listeners; + + private ScheduledExecutorService executor; + + @Inject + public MidnightTimer() + { + this.listeners = new LinkedList<>(); + } + + public synchronized void addListener(MidnightListener listener) + { + this.listeners.add(listener); + } + + public synchronized void onPause() + { + executor.shutdownNow(); + } + + public synchronized void onResume() + { + executor = Executors.newSingleThreadScheduledExecutor(); + executor.scheduleAtFixedRate(() -> notifyListeners(), + DateUtils.millisecondsUntilTomorrow() + 1000, + DateUtils.millisecondsInOneDay, TimeUnit.MILLISECONDS); + } + + public synchronized void removeListener(MidnightListener listener) + { + this.listeners.remove(listener); + } + + private synchronized void notifyListeners() + { + for (MidnightListener l : listeners) l.atMidnight(); + } + + public interface MidnightListener + { + void atMidnight(); + } +} From 4fc30fae53f9f45cc6069e68710ef42f41ef1171 Mon Sep 17 00:00:00 2001 From: Anirudha Agashe Date: Tue, 10 Jan 2017 09:39:05 +0530 Subject: [PATCH 9/9] Remove use of static context from HabitsApplication class (#224) Remove static context from application class. Replace static method to obtain context with appropriate dependency. Remove context from controller. Add auto factory to export db task. --- .../java/org/isoron/uhabits/BaseViewTest.java | 2 +- .../org/isoron/uhabits/io/ImportTest.java | 2 +- .../uhabits/tasks/ExportCSVTaskTest.java | 2 +- .../uhabits/tasks/ExportDBTaskTest.java | 2 +- .../org/isoron/uhabits/HabitsApplication.java | 26 +++++-------------- .../isoron/uhabits/activities/BaseSystem.java | 2 +- .../habits/list/ListHabitsController.java | 9 +++++-- .../habits/list/views/HabitCardView.java | 2 ++ .../org/isoron/uhabits/io/LoopDBImporter.java | 12 ++++++--- .../models/sqlite/records/HabitRecord.java | 2 ++ .../uhabits/receivers/PebbleReceiver.java | 7 ++++- .../isoron/uhabits/tasks/ExportCSVTask.java | 12 +++++++-- .../isoron/uhabits/tasks/ExportDBTask.java | 17 +++++++++--- .../isoron/uhabits/utils/DatabaseUtils.java | 10 +++---- .../org/isoron/uhabits/utils/FileUtils.java | 3 +-- .../habits/list/ListHabitsControllerTest.java | 5 +++- 16 files changed, 71 insertions(+), 44 deletions(-) diff --git a/app/src/androidTest/java/org/isoron/uhabits/BaseViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/BaseViewTest.java index 689cafcaa..b886a5675 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/BaseViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/BaseViewTest.java @@ -225,7 +225,7 @@ public class BaseViewTest extends BaseAndroidTest throws IOException { File dir = FileUtils.getSDCardDir("test-screenshots"); - if (dir == null) dir = FileUtils.getFilesDir("test-screenshots"); + if (dir == null) dir = FileUtils.getFilesDir(testContext,"test-screenshots"); if (dir == null) throw new RuntimeException( "Could not find suitable dir for screenshots"); diff --git a/app/src/androidTest/java/org/isoron/uhabits/io/ImportTest.java b/app/src/androidTest/java/org/isoron/uhabits/io/ImportTest.java index 6dcdf434d..a511b36f0 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/io/ImportTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/io/ImportTest.java @@ -53,7 +53,7 @@ public class ImportTest extends BaseAndroidTest fixtures.purgeHabits(habitList); context = InstrumentationRegistry.getInstrumentation().getContext(); - baseDir = FileUtils.getFilesDir("Backups"); + baseDir = FileUtils.getFilesDir(context, "Backups"); if (baseDir == null) fail("baseDir should not be null"); } diff --git a/app/src/androidTest/java/org/isoron/uhabits/tasks/ExportCSVTaskTest.java b/app/src/androidTest/java/org/isoron/uhabits/tasks/ExportCSVTaskTest.java index 86860f006..56bf83c89 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/tasks/ExportCSVTaskTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/tasks/ExportCSVTaskTest.java @@ -56,7 +56,7 @@ public class ExportCSVTaskTest extends BaseAndroidTest for (Habit h : habitList) selected.add(h); taskRunner.execute( - new ExportCSVTask(habitList, selected, archiveFilename -> { + new ExportCSVTask(testContext,habitList, selected, archiveFilename -> { assertThat(archiveFilename, is(not(nullValue()))); File f = new File(archiveFilename); assertTrue(f.exists()); diff --git a/app/src/androidTest/java/org/isoron/uhabits/tasks/ExportDBTaskTest.java b/app/src/androidTest/java/org/isoron/uhabits/tasks/ExportDBTaskTest.java index ac3dce2ba..42ade59aa 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/tasks/ExportDBTaskTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/tasks/ExportDBTaskTest.java @@ -46,7 +46,7 @@ public class ExportDBTaskTest extends BaseAndroidTest @Test public void testExportCSV() throws Throwable { - ExportDBTask task = new ExportDBTask(filename -> { + ExportDBTask task = new ExportDBTask(testContext, filename -> { assertThat(filename, is(not(nullValue()))); File f = new File(filename); diff --git a/app/src/main/java/org/isoron/uhabits/HabitsApplication.java b/app/src/main/java/org/isoron/uhabits/HabitsApplication.java index 692cd0402..bad1c4ee5 100644 --- a/app/src/main/java/org/isoron/uhabits/HabitsApplication.java +++ b/app/src/main/java/org/isoron/uhabits/HabitsApplication.java @@ -38,7 +38,7 @@ import java.io.*; */ public class HabitsApplication extends Application { - private static Context context; + private Context context; private static AppComponent component; @@ -58,26 +58,14 @@ public class HabitsApplication extends Application HabitsApplication.component = component; } - @NonNull - @Deprecated - public static Context getContext() - { - if (context == null) throw new RuntimeException("context is null"); - return context; - } - public static boolean isTestMode() { try { - if (context != null) - { - String testClass = "org.isoron.uhabits.BaseAndroidTest"; - context.getClassLoader().loadClass(testClass); - } + Class.forName ("org.isoron.uhabits.BaseAndroidTest"); return true; } - catch (final Exception e) + catch (final ClassNotFoundException e) { return false; } @@ -87,7 +75,7 @@ public class HabitsApplication extends Application public void onCreate() { super.onCreate(); - HabitsApplication.context = this; + context = this; component = DaggerAppComponent .builder() @@ -96,11 +84,11 @@ public class HabitsApplication extends Application if (isTestMode()) { - File db = DatabaseUtils.getDatabaseFile(); + File db = DatabaseUtils.getDatabaseFile(context); if (db.exists()) db.delete(); } - DatabaseUtils.initializeActiveAndroid(); + DatabaseUtils.initializeActiveAndroid(context); widgetUpdater = component.getWidgetUpdater(); widgetUpdater.startListening(); @@ -125,7 +113,7 @@ public class HabitsApplication extends Application @Override public void onTerminate() { - HabitsApplication.context = null; + context = null; ActiveAndroid.dispose(); reminderScheduler.stopListening(); diff --git a/app/src/main/java/org/isoron/uhabits/activities/BaseSystem.java b/app/src/main/java/org/isoron/uhabits/activities/BaseSystem.java index 044fa9600..bacf911b3 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/BaseSystem.java +++ b/app/src/main/java/org/isoron/uhabits/activities/BaseSystem.java @@ -70,7 +70,7 @@ public class BaseSystem if (context == null) throw new RuntimeException( "application context should not be null"); - File dir = FileUtils.getFilesDir("Logs"); + File dir = FileUtils.getFilesDir(context, "Logs"); if (dir == null) throw new IOException("log dir should not be null"); File logFile = diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java index 1d6462893..a52c4b4d6 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java @@ -41,6 +41,7 @@ import javax.inject.*; public class ListHabitsController implements HabitCardListController.HabitListener { + @NonNull private final ListHabitsScreen screen; @@ -70,6 +71,8 @@ public class ListHabitsController private ExportCSVTaskFactory exportCSVFactory; + private ExportDBTaskFactory exportDBFactory; + @Inject public ListHabitsController(@NonNull BaseSystem system, @NonNull CommandRunner commandRunner, @@ -82,7 +85,8 @@ public class ListHabitsController @NonNull WidgetUpdater widgetUpdater, @NonNull ImportDataTaskFactory importTaskFactory, - @NonNull ExportCSVTaskFactory exportCSVFactory) + @NonNull ExportCSVTaskFactory exportCSVFactory, + @NonNull ExportDBTaskFactory exportDBFactory) { this.adapter = adapter; this.commandRunner = commandRunner; @@ -95,6 +99,7 @@ public class ListHabitsController this.widgetUpdater = widgetUpdater; this.importTaskFactory = importTaskFactory; this.exportCSVFactory = exportCSVFactory; + this.exportDBFactory = exportDBFactory; } public void onExportCSV() @@ -110,7 +115,7 @@ public class ListHabitsController public void onExportDB() { - taskRunner.execute(new ExportDBTask(filename -> { + taskRunner.execute(exportDBFactory.create(filename -> { if (filename != null) screen.showSendFileScreen(filename); else screen.showMessage(R.string.could_not_export); })); diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java index abcebb79f..dec6d04b8 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java @@ -24,6 +24,8 @@ import android.content.*; import android.graphics.drawable.*; import android.os.*; import android.support.annotation.*; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.util.*; import android.widget.*; diff --git a/app/src/main/java/org/isoron/uhabits/io/LoopDBImporter.java b/app/src/main/java/org/isoron/uhabits/io/LoopDBImporter.java index 69fd1d4f0..a3ebcf882 100644 --- a/app/src/main/java/org/isoron/uhabits/io/LoopDBImporter.java +++ b/app/src/main/java/org/isoron/uhabits/io/LoopDBImporter.java @@ -19,12 +19,14 @@ package org.isoron.uhabits.io; +import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.support.annotation.NonNull; import com.activeandroid.ActiveAndroid; +import org.isoron.uhabits.AppContext; import org.isoron.uhabits.models.*; import org.isoron.uhabits.utils.DatabaseUtils; import org.isoron.uhabits.utils.FileUtils; @@ -39,10 +41,14 @@ import javax.inject.*; */ public class LoopDBImporter extends AbstractImporter { + @NonNull + private Context context; + @Inject - public LoopDBImporter(@NonNull HabitList habits) + public LoopDBImporter(@NonNull @AppContext Context context, @NonNull HabitList habits) { super(habits); + this.context = context; } @Override @@ -68,8 +74,8 @@ public class LoopDBImporter extends AbstractImporter public void importHabitsFromFile(@NonNull File file) throws IOException { ActiveAndroid.dispose(); - File originalDB = DatabaseUtils.getDatabaseFile(); + File originalDB = DatabaseUtils.getDatabaseFile(context); FileUtils.copy(file, originalDB); - DatabaseUtils.initializeActiveAndroid(); + DatabaseUtils.initializeActiveAndroid(context); } } diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/HabitRecord.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/HabitRecord.java index 71f4cbdd3..b4120a386 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/HabitRecord.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/HabitRecord.java @@ -22,6 +22,8 @@ package org.isoron.uhabits.models.sqlite.records; import android.annotation.*; import android.database.*; import android.support.annotation.*; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import com.activeandroid.*; import com.activeandroid.annotation.*; diff --git a/app/src/main/java/org/isoron/uhabits/receivers/PebbleReceiver.java b/app/src/main/java/org/isoron/uhabits/receivers/PebbleReceiver.java index ce0ab7eed..55243f015 100644 --- a/app/src/main/java/org/isoron/uhabits/receivers/PebbleReceiver.java +++ b/app/src/main/java/org/isoron/uhabits/receivers/PebbleReceiver.java @@ -40,6 +40,9 @@ public class PebbleReceiver extends PebbleDataReceiver public static final UUID WATCHAPP_UUID = UUID.fromString("82629d99-8ea6-4631-a022-9ca77a12a058"); + @NonNull + private Context context; + private HabitList allHabits; private CommandRunner commandRunner; @@ -61,6 +64,8 @@ public class PebbleReceiver extends PebbleDataReceiver if (context == null) throw new RuntimeException("context is null"); if (data == null) throw new RuntimeException("data is null"); + this.context = context; + HabitsApplication app = (HabitsApplication) context.getApplicationContext(); @@ -136,7 +141,7 @@ public class PebbleReceiver extends PebbleDataReceiver private void sendDict(@NonNull PebbleDictionary dict) { - PebbleKit.sendDataToPebble(HabitsApplication.getContext(), + PebbleKit.sendDataToPebble(context, PebbleReceiver.WATCHAPP_UUID, dict); } diff --git a/app/src/main/java/org/isoron/uhabits/tasks/ExportCSVTask.java b/app/src/main/java/org/isoron/uhabits/tasks/ExportCSVTask.java index 27f81f0b5..1f41f1994 100644 --- a/app/src/main/java/org/isoron/uhabits/tasks/ExportCSVTask.java +++ b/app/src/main/java/org/isoron/uhabits/tasks/ExportCSVTask.java @@ -19,10 +19,13 @@ package org.isoron.uhabits.tasks; +import android.content.Context; import android.support.annotation.*; import com.google.auto.factory.*; +import org.isoron.uhabits.AppContext; +import org.isoron.uhabits.activities.ActivityContext; import org.isoron.uhabits.io.*; import org.isoron.uhabits.models.*; import org.isoron.uhabits.utils.*; @@ -35,6 +38,9 @@ public class ExportCSVTask implements Task { private String archiveFilename; + @NonNull + private final Context context; + @NonNull private final List selectedHabits; @@ -44,10 +50,12 @@ public class ExportCSVTask implements Task @NonNull private final HabitList habitList; - public ExportCSVTask(@Provided @NonNull HabitList habitList, + public ExportCSVTask(@Provided @AppContext @NonNull Context context, + @Provided @NonNull HabitList habitList, @NonNull List selectedHabits, @NonNull Listener listener) { + this.context = context; this.listener = listener; this.habitList = habitList; this.selectedHabits = selectedHabits; @@ -58,7 +66,7 @@ public class ExportCSVTask implements Task { try { - File dir = FileUtils.getFilesDir("CSV"); + File dir = FileUtils.getFilesDir(context, "CSV"); if (dir == null) return; HabitsCSVExporter exporter; diff --git a/app/src/main/java/org/isoron/uhabits/tasks/ExportDBTask.java b/app/src/main/java/org/isoron/uhabits/tasks/ExportDBTask.java index b70ee65d4..26f523194 100644 --- a/app/src/main/java/org/isoron/uhabits/tasks/ExportDBTask.java +++ b/app/src/main/java/org/isoron/uhabits/tasks/ExportDBTask.java @@ -19,22 +19,33 @@ package org.isoron.uhabits.tasks; +import android.content.Context; import android.support.annotation.*; +import com.google.auto.factory.AutoFactory; +import com.google.auto.factory.Provided; + +import org.isoron.uhabits.AppContext; +import org.isoron.uhabits.activities.ActivityContext; import org.isoron.uhabits.utils.*; import java.io.*; +@AutoFactory(allowSubclasses = true) public class ExportDBTask implements Task { private String filename; + @NonNull + private Context context; + @NonNull private final Listener listener; - public ExportDBTask(@NonNull Listener listener) + public ExportDBTask(@Provided @AppContext @NonNull Context context, @NonNull Listener listener) { this.listener = listener; + this.context = context; } @Override @@ -44,10 +55,10 @@ public class ExportDBTask implements Task try { - File dir = FileUtils.getFilesDir("Backups"); + File dir = FileUtils.getFilesDir(context, "Backups"); if (dir == null) return; - filename = DatabaseUtils.saveDatabaseCopy(dir); + filename = DatabaseUtils.saveDatabaseCopy(context, dir); } catch (IOException e) { diff --git a/app/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.java b/app/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.java index 0a7c0fbbf..5e6d9e97a 100644 --- a/app/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.java +++ b/app/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.java @@ -47,9 +47,8 @@ public abstract class DatabaseUtils } @NonNull - public static File getDatabaseFile() + public static File getDatabaseFile(Context context) { - Context context = HabitsApplication.getContext(); String databaseFilename = getDatabaseFilename(); String root = context.getFilesDir().getPath(); @@ -68,9 +67,8 @@ public abstract class DatabaseUtils } @SuppressWarnings("unchecked") - public static void initializeActiveAndroid() + public static void initializeActiveAndroid(Context context) { - Context context = HabitsApplication.getContext(); Configuration dbConfig = new Configuration.Builder(context) .setDatabaseName(getDatabaseFilename()) .setDatabaseVersion(BuildConfig.databaseVersion) @@ -82,14 +80,14 @@ public abstract class DatabaseUtils } @SuppressWarnings("ResultOfMethodCallIgnored") - public static String saveDatabaseCopy(File dir) throws IOException + public static String saveDatabaseCopy(Context context, File dir) throws IOException { SimpleDateFormat dateFormat = DateFormats.getBackupDateFormat(); String date = dateFormat.format(DateUtils.getLocalTime()); String format = "%s/Loop Habits Backup %s.db"; String filename = String.format(format, dir.getAbsolutePath(), date); - File db = getDatabaseFile(); + File db = getDatabaseFile(context); File dbCopy = new File(filename); FileUtils.copy(db, dbCopy); diff --git a/app/src/main/java/org/isoron/uhabits/utils/FileUtils.java b/app/src/main/java/org/isoron/uhabits/utils/FileUtils.java index 4bc4a5d76..4cee13ac3 100644 --- a/app/src/main/java/org/isoron/uhabits/utils/FileUtils.java +++ b/app/src/main/java/org/isoron/uhabits/utils/FileUtils.java @@ -87,9 +87,8 @@ public abstract class FileUtils } @Nullable - public static File getFilesDir(@Nullable String relativePath) + public static File getFilesDir(@NonNull Context context, @Nullable String relativePath) { - Context context = HabitsApplication.getContext(); File externalFilesDirs[] = ContextCompat.getExternalFilesDirs(context, null); diff --git a/app/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsControllerTest.java b/app/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsControllerTest.java index ecba15c00..74b71a74b 100644 --- a/app/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsControllerTest.java +++ b/app/src/test/java/org/isoron/uhabits/activities/habits/list/ListHabitsControllerTest.java @@ -57,6 +57,8 @@ public class ListHabitsControllerTest extends BaseUnitTest private ExportCSVTaskFactory exportCSVFactory; + private ExportDBTaskFactory exportDBFactory; + @Override public void setUp() { @@ -73,11 +75,12 @@ public class ListHabitsControllerTest extends BaseUnitTest widgetUpdater = mock(WidgetUpdater.class); importTaskFactory = mock(ImportDataTaskFactory.class); exportCSVFactory = mock(ExportCSVTaskFactory.class); + exportDBFactory = mock(ExportDBTaskFactory.class); controller = spy(new ListHabitsController(system, commandRunner, habitList, adapter, screen, prefs, reminderScheduler, taskRunner, - widgetUpdater, importTaskFactory, exportCSVFactory)); + widgetUpdater, importTaskFactory, exportCSVFactory, exportDBFactory)); } @Test