diff --git a/app/src/androidTest/assets/icon.png b/app/src/androidTest/assets/icon.png new file mode 100644 index 000000000..7907954db Binary files /dev/null and b/app/src/androidTest/assets/icon.png differ diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/io/ImportTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/io/ImportTest.java index c6dcfcbae..99208a7c5 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/io/ImportTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/io/ImportTest.java @@ -52,17 +52,13 @@ public class ImportTest { private File baseDir; private Context context; - private Context targetContext; @Before public void setup() { HabitFixtures.purgeHabits(); - context = InstrumentationRegistry.getInstrumentation().getContext(); - targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - - baseDir = DatabaseHelper.getFilesDir(targetContext, "Backups"); + baseDir = DatabaseHelper.getFilesDir("Backups"); if(baseDir == null) fail("baseDir should not be null"); } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportCSVTaskTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportCSVTaskTest.java new file mode 100644 index 000000000..a899a86f0 --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportCSVTaskTest.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.unit.tasks; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.SmallTest; +import android.widget.ProgressBar; + +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.tasks.ExportCSVTask; +import org.isoron.uhabits.unit.models.HabitFixtures; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static junit.framework.Assert.assertTrue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.core.IsNot.not; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ExportCSVTaskTest +{ + @Test + public void exportCSV() throws InterruptedException + { + Context context = InstrumentationRegistry.getContext(); + final CountDownLatch latch = new CountDownLatch(1); + + HabitFixtures.createNonDailyHabit(); + List habits = Habit.getAll(true); + ProgressBar bar = new ProgressBar(context); + + ExportCSVTask task = new ExportCSVTask(habits, bar); + task.setListener(new ExportCSVTask.Listener() + { + @Override + public void onExportCSVFinished(String archiveFilename) + { + assertThat(archiveFilename, is(not(nullValue()))); + + File f = new File(archiveFilename); + assertTrue(f.exists()); + assertTrue(f.canRead()); + latch.countDown(); + } + }); + + task.execute(); + latch.await(30, TimeUnit.SECONDS); + } +} diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportDBTaskTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportDBTaskTest.java new file mode 100644 index 000000000..e744b5ede --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportDBTaskTest.java @@ -0,0 +1,71 @@ +/* + * 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.unit.tasks; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.SmallTest; +import android.widget.ProgressBar; + +import org.isoron.uhabits.tasks.ExportDBTask; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static junit.framework.Assert.assertTrue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.core.IsNot.not; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ExportDBTaskTest +{ + @Test + public void exportCSV() throws InterruptedException + { + Context context = InstrumentationRegistry.getContext(); + final CountDownLatch latch = new CountDownLatch(1); + + ProgressBar bar = new ProgressBar(context); + ExportDBTask task = new ExportDBTask(bar); + task.setListener(new ExportDBTask.Listener() + { + @Override + public void onExportDBFinished(String filename) + { + assertThat(filename, is(not(nullValue()))); + + File f = new File(filename); + assertTrue(f.exists()); + assertTrue(f.canRead()); + latch.countDown(); + } + }); + + task.execute(); + latch.await(30, TimeUnit.SECONDS); + } +} diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java new file mode 100644 index 000000000..dc3f0681a --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java @@ -0,0 +1,108 @@ +/* + * 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.unit.tasks; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.SmallTest; +import android.widget.ProgressBar; + +import org.isoron.uhabits.helpers.DatabaseHelper; +import org.isoron.uhabits.tasks.ImportDataTask; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.fail; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ImportDataTaskTest +{ + private Context context; + private File baseDir; + + @Before + public void setup() + { + context = InstrumentationRegistry.getContext(); + + baseDir = DatabaseHelper.getFilesDir("Backups"); + if(baseDir == null) fail("baseDir should not be null"); + } + + private void copyAssetToFile(String assetPath, File dst) throws IOException + { + InputStream in = context.getAssets().open(assetPath); + DatabaseHelper.copy(in, dst); + } + + private void assertTaskResult(final int expectedResult, String assetFilename) + throws IOException, InterruptedException + { + final CountDownLatch latch = new CountDownLatch(1); + ImportDataTask task = createTask(assetFilename); + + task.setListener(new ImportDataTask.Listener() + { + @Override + public void onImportFinished(int result) + { + assertThat(result, equalTo(expectedResult)); + latch.countDown(); + } + }); + + task.execute(); + latch.await(30, TimeUnit.SECONDS); + } + + @NonNull + private ImportDataTask createTask(String assetFilename) throws IOException + { + ProgressBar bar = new ProgressBar(context); + File file = new File(String.format("%s/%s", baseDir.getPath(), assetFilename)); + copyAssetToFile(assetFilename, file); + + return new ImportDataTask(file, bar); + } + + @Test + public void importInvalidData() throws Throwable + { + assertTaskResult(ImportDataTask.NOT_RECOGNIZED, "icon.png"); + } + + @Test + public void importValidData() throws Throwable + { + assertTaskResult(ImportDataTask.SUCCESS, "loop.db"); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/dialogs/FilePickerDialog.java b/app/src/main/java/org/isoron/uhabits/dialogs/FilePickerDialog.java index a7a8520fd..94c574fbd 100644 --- a/app/src/main/java/org/isoron/uhabits/dialogs/FilePickerDialog.java +++ b/app/src/main/java/org/isoron/uhabits/dialogs/FilePickerDialog.java @@ -48,7 +48,7 @@ public class FilePickerDialog implements AdapterView.OnItemClickListener void onFileSelected(File file); } - private OnFileSelectedListener fileListener; + private OnFileSelectedListener listener; public FilePickerDialog(Activity activity, File initialDirectory) { @@ -81,7 +81,7 @@ public class FilePickerDialog implements AdapterView.OnItemClickListener } else { - if (fileListener != null) fileListener.onFileSelected(file); + if (listener != null) listener.onFileSelected(file); dialog.dismiss(); } } @@ -91,9 +91,9 @@ public class FilePickerDialog implements AdapterView.OnItemClickListener dialog.show(); } - public void setFileListener(OnFileSelectedListener fileListener) + public void setListener(OnFileSelectedListener listener) { - this.fileListener = fileListener; + this.listener = listener; } private void navigateTo(File path) diff --git a/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java b/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java index 22a0be019..02ca46b92 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java +++ b/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java @@ -26,6 +26,7 @@ import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceManager; +import android.support.annotation.Nullable; import android.view.ActionMode; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; @@ -73,7 +74,8 @@ import java.util.List; public class ListHabitsFragment extends Fragment implements OnSavedListener, OnItemClickListener, OnLongClickListener, DropListener, OnClickListener, HabitListLoader.Listener, AdapterView.OnItemLongClickListener, - HabitSelectionCallback.Listener, ImportDataTask.Listener + HabitSelectionCallback.Listener, ImportDataTask.Listener, ExportCSVTask.Listener, + ExportDBTask.Listener { long lastLongClick = 0; private boolean isShortToggleEnabled; @@ -437,7 +439,7 @@ public class ListHabitsFragment extends Fragment if(dir == null) return; FilePickerDialog picker = new FilePickerDialog(activity, dir); - picker.setFileListener(new FilePickerDialog.OnFileSelectedListener() + picker.setListener(new FilePickerDialog.OnFileSelectedListener() { @Override public void onFileSelected(File file) @@ -447,6 +449,7 @@ public class ListHabitsFragment extends Fragment task.execute(); } }); + picker.show(); } @@ -472,11 +475,49 @@ public class ListHabitsFragment extends Fragment public void exportAllHabits() { - new ExportCSVTask(activity, Habit.getAll(true), progressBar).execute(); + ExportCSVTask task = new ExportCSVTask(Habit.getAll(true), progressBar); + task.setListener(this); + task.execute(); + } + + @Override + public void onExportCSVFinished(@Nullable String archiveFilename) + { + if(archiveFilename != null) + { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_SEND); + intent.setType("application/zip"); + intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(archiveFilename))); + activity.startActivity(intent); + } + else + { + activity.showToast(R.string.could_not_export); + } } public void exportDB() { - new ExportDBTask(activity, progressBar).execute(); + ExportDBTask task = new ExportDBTask(progressBar); + task.setListener(this); + task.execute(); + } + + @Override + public void onExportDBFinished(@Nullable String filename) + { + if(filename != null) + { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_SEND); + intent.setType("application/octet-stream"); + intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(filename))); + activity.startActivity(intent); + } + else + { + activity.showToast(R.string.could_not_export); + } } } diff --git a/app/src/main/java/org/isoron/uhabits/helpers/DatabaseHelper.java b/app/src/main/java/org/isoron/uhabits/helpers/DatabaseHelper.java index cde9ca9f2..cd411328f 100644 --- a/app/src/main/java/org/isoron/uhabits/helpers/DatabaseHelper.java +++ b/app/src/main/java/org/isoron/uhabits/helpers/DatabaseHelper.java @@ -124,8 +124,11 @@ public class DatabaseHelper } @Nullable - public static File getFilesDir(Context context, String prefix) + public static File getFilesDir(String prefix) { + Context context = HabitsApplication.getContext(); + if(context == null) return null; + File chosenDir = null; File externalFilesDirs[] = ContextCompat.getExternalFilesDirs(context, null); if(externalFilesDirs == null) return null; 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 151abb242..2ebd05cc8 100644 --- a/app/src/main/java/org/isoron/uhabits/tasks/ExportCSVTask.java +++ b/app/src/main/java/org/isoron/uhabits/tasks/ExportCSVTask.java @@ -19,14 +19,11 @@ package org.isoron.uhabits.tasks; -import android.content.Intent; -import android.net.Uri; import android.os.AsyncTask; +import android.support.annotation.Nullable; import android.view.View; import android.widget.ProgressBar; -import org.isoron.uhabits.R; -import org.isoron.uhabits.ReplayableActivity; import org.isoron.uhabits.helpers.DatabaseHelper; import org.isoron.uhabits.io.HabitsCSVExporter; import org.isoron.uhabits.models.Habit; @@ -37,17 +34,25 @@ import java.util.List; public class ExportCSVTask extends AsyncTask { - private final ReplayableActivity activity; + public interface Listener + { + void onExportCSVFinished(@Nullable String archiveFilename); + } + private ProgressBar progressBar; private final List selectedHabits; - String archiveFilename; + private String archiveFilename; + private ExportCSVTask.Listener listener; - public ExportCSVTask(ReplayableActivity activity, List selectedHabits, - ProgressBar progressBar) + public ExportCSVTask(List selectedHabits, ProgressBar progressBar) { this.selectedHabits = selectedHabits; this.progressBar = progressBar; - this.activity = activity; + } + + public void setListener(Listener listener) + { + this.listener = listener; } @Override @@ -63,19 +68,8 @@ public class ExportCSVTask extends AsyncTask @Override protected void onPostExecute(Void aVoid) { - if(archiveFilename != null) - { - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_SEND); - intent.setType("application/zip"); - intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(archiveFilename))); - - activity.startActivity(intent); - } - else - { - activity.showToast(R.string.could_not_export); - } + if(listener != null) + listener.onExportCSVFinished(archiveFilename); if(progressBar != null) progressBar.setVisibility(View.GONE); @@ -86,7 +80,7 @@ public class ExportCSVTask extends AsyncTask { try { - File dir = DatabaseHelper.getFilesDir(activity, "CSV"); + File dir = DatabaseHelper.getFilesDir("CSV"); if(dir == null) return null; HabitsCSVExporter exporter = new HabitsCSVExporter(selectedHabits, dir); 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 f5f70809f..abc31b490 100644 --- a/app/src/main/java/org/isoron/uhabits/tasks/ExportDBTask.java +++ b/app/src/main/java/org/isoron/uhabits/tasks/ExportDBTask.java @@ -19,14 +19,11 @@ package org.isoron.uhabits.tasks; -import android.content.Intent; -import android.net.Uri; import android.os.AsyncTask; +import android.support.annotation.Nullable; import android.view.View; import android.widget.ProgressBar; -import org.isoron.uhabits.R; -import org.isoron.uhabits.ReplayableActivity; import org.isoron.uhabits.helpers.DatabaseHelper; import java.io.File; @@ -34,14 +31,23 @@ import java.io.IOException; public class ExportDBTask extends AsyncTask { - private final ReplayableActivity activity; + public interface Listener + { + void onExportDBFinished(@Nullable String filename); + } + private ProgressBar progressBar; private String filename; + private Listener listener; - public ExportDBTask(ReplayableActivity activity, ProgressBar progressBar) + public ExportDBTask(ProgressBar progressBar) { this.progressBar = progressBar; - this.activity = activity; + } + + public void setListener(Listener listener) + { + this.listener = listener; } @Override @@ -57,19 +63,8 @@ public class ExportDBTask extends AsyncTask @Override protected void onPostExecute(Void aVoid) { - if(filename != null) - { - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_SEND); - intent.setType("application/octet-stream"); - intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(filename))); - - activity.startActivity(intent); - } - else - { - activity.showToast(R.string.could_not_export); - } + if(listener != null) + listener.onExportDBFinished(filename); if(progressBar != null) progressBar.setVisibility(View.GONE); @@ -82,7 +77,7 @@ public class ExportDBTask extends AsyncTask try { - File dir = DatabaseHelper.getFilesDir(activity, "Backups"); + File dir = DatabaseHelper.getFilesDir("Backups"); if(dir == null) return null; filename = DatabaseHelper.saveDatabaseCopy(dir); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 43a91f004..394b5dd39 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -139,13 +139,13 @@ Custom … Help & FAQ Failed to export data. - Failed to import habits from file. - File type not recognized. + Failed to import data. + File not recognized. Habits imported successfully. - Supports full backups exported by this app, as well as files generated by Tickmate, HabitBull or Rewire. See FAQ for more information. + Full backup successfully exported. Import data - Generates files that can be opened by spreadsheet software such as Microsoft Excel or OpenOffice Calc, but cannot be imported back. Export full backup - Generates a file that contains all your data, and that can be imported back. - Full backup successfully exported. + Supports full backups exported by this app, as well as files generated by Tickmate, HabitBull or Rewire. See FAQ for more information. + Generates files that can be opened by spreadsheet software such as Microsoft Excel or OpenOffice Calc. This file cannot be imported back. + Generates a file that contains all your data. This file can be imported back. \ No newline at end of file