From d9f6eaa3391a755d21cb1ef1ea34f385ba957ed5 Mon Sep 17 00:00:00 2001 From: Bradlee Speice Date: Sat, 14 Jun 2014 16:43:21 -0400 Subject: [PATCH] Squashed commit of the following: (enable Rx) commit 4269988b7c300f3de1b5befb0f3b221150401dd1 Author: Bradlee Speice Date: Sat Jun 14 16:41:11 2014 -0400 Backport to Java 6... Android Studio automatically folds lambda-style, so it won't get too out of hand. commit e945ef51a7be533eb451cefba5489343722a5637 Author: Bradlee Speice Date: Sat Jun 14 16:25:13 2014 -0400 Get the unit tests passing again Note: I need to write more. Lots more. commit 04fe4d13b47a3f80aa4bcd5d5d1efe0fe88aec5a Author: Bradlee Speice Date: Sat Jun 14 15:13:44 2014 -0400 Add lots of Lint fixes commit dda5c792997c148c720c8bf75a554c9001202e98 Author: Bradlee Speice Date: Sat Jun 14 15:04:17 2014 -0400 Fix books not being removed... commit 93abe065a29c43d398efd83de212297a218e1f7d Author: Bradlee Speice Date: Sat Jun 14 14:10:43 2014 -0400 Fix a NetworkOnMainThreadException commit ba3c6ebe6c1f203da28cfd98806cc51256d0efa5 Author: Bradlee Speice Date: Sat Jun 14 14:08:50 2014 -0400 Some refactoring and Async fixes. commit 3869cf0b9b40a075807c2a3401e392db07daf2fc Author: Bradlee Speice Date: Tue Jun 10 23:46:29 2014 -0400 Synchronization needs fixing, otherwise works. commit 8d17b6db642a3dbedc8dbc91511ba160f57870ef Author: Bradlee Speice Date: Tue Jun 10 23:44:35 2014 -0400 Silly DownloadManager Injects are for classes that need them. commit 7070c933d1600bf6bdaf18ef149067cb343b8fc7 Author: Bradlee Speice Date: Tue Jun 10 23:41:35 2014 -0400 Fix the dagger compile errors If you have an @Singleton with no @Injects inside it, you need to add an @Injects constructor for Dagger to validate. commit 28dfec81d71993a2610e494be6d60eeb3620f909 Author: Bradlee Speice Date: Tue Jun 10 23:17:20 2014 -0400 [broken probably] Refactoring to Rx should be done... But having issues with compiling. Checking if Dagger and retrolambda play nice. commit fb0c5fdaaa02cbf9efbe664ae49134f3dffbcbac Author: Bradlee Speice Date: Tue Jun 10 22:56:52 2014 -0400 [broken] BookListFragment to Rx commit 6eb5f66dcc6e3604628718cf825b9d2e07a941aa Author: Bradlee Speice Date: Tue Jun 10 22:49:47 2014 -0400 [broken] BookItemHolder to Rx commit e356c8d1fceed5703e48773f36464e97b370aa4d Author: Bradlee Speice Date: Tue Jun 10 22:40:46 2014 -0400 Revert "[broken] Remove the BookDownloadThread again" This reverts commit 8f346f17e4caad22d087db4e91a93505e6f2b68e. commit 287b8cb40d6fe12725112967219e28b17b680172 Author: Bradlee Speice Date: Tue Jun 10 22:39:06 2014 -0400 [broken] Add a note on the InstalledManager commit 899b054c8b2a1baf00a8ac7fbfbcac247dec7754 Author: Bradlee Speice Date: Tue Jun 10 22:37:53 2014 -0400 [broken] Slight semantic change to RefreshManager commit 8f346f17e4caad22d087db4e91a93505e6f2b68e Author: Bradlee Speice Date: Tue Jun 10 22:36:12 2014 -0400 [broken] Remove the BookDownloadThread again commit 1a7364da86d3e94f7ad9f50e13c72cbca6ffd4d6 Author: Bradlee Speice Date: Tue Jun 10 22:35:40 2014 -0400 [broken] Convert BookDownloadManager to Rx commit ca1ccd994243a38ba3c1576ba8777e6c387e9200 Author: Bradlee Speice Date: Tue Jun 10 22:04:53 2014 -0400 [broken] Convert RefreshManager to Rx commit 5770e8dd748f894f8fcf16f91a71bf9549dcbcb3 Author: Bradlee Speice Date: Tue Jun 10 19:32:23 2014 -0400 Add RxAndroid support, remove eventbus commit 3f5909be082ca7d00038f6bed289239a171244ef Author: Bradlee Speice Date: Tue Jun 10 19:30:45 2014 -0400 Add retrolambda support --- MinimalBible/build.gradle | 32 +++-- .../minimalbible/activities/BaseFragment.java | 2 +- .../downloader/ActivityDownloaderModule.java | 14 +-- .../activities/downloader/BookItemHolder.java | 36 ++++-- .../downloader/BookListAdapter.java | 4 +- .../downloader/BookListFragment.java | 88 ++++++------- .../downloader/DownloadActivity.java | 10 +- .../manager/BookDownloadManager.java | 24 ++-- .../manager/BookDownloadThread.java | 39 +++--- .../downloader/manager/BookRefreshTask.java | 86 ------------- .../downloader/manager/DLProgressEvent.java | 4 +- .../downloader/manager/DownloadManager.java | 28 +---- .../downloader/manager/EventBookList.java | 33 ----- .../downloader/manager/InstalledManager.java | 25 +++- .../downloader/manager/RefreshManager.java | 118 ++++++++++++++---- .../test/DownloadActivityTest.java | 70 ++++------- appcompat_v7/build.gradle | 2 +- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 +- 19 files changed, 270 insertions(+), 351 deletions(-) delete mode 100644 MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/BookRefreshTask.java delete mode 100644 MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/EventBookList.java diff --git a/MinimalBible/build.gradle b/MinimalBible/build.gradle index a6b2d98..7df2a72 100644 --- a/MinimalBible/build.gradle +++ b/MinimalBible/build.gradle @@ -1,12 +1,14 @@ apply plugin: 'android' apply plugin: 'android-apt' +//apply plugin: 'retrolambda' buildscript { repositories { mavenCentral() } dependencies { - classpath 'com.neenbedankt.gradle.plugins:android-apt:1.2+' + classpath 'com.neenbedankt.gradle.plugins:android-apt:1.3' + //classpath 'me.tatarka:gradle-retrolambda:1.3.2' } } @@ -17,24 +19,18 @@ repositories { dependencies { compile project(path: ':jsword-minimalbible', configuration: 'buildJSword') compile project(':appcompat_v7') - apt 'com.squareup.dagger:dagger-compiler:1.2.0' compile 'com.squareup.dagger:dagger:1.2.0' - apt 'com.jakewharton:butterknife:5.0.1' compile 'com.jakewharton:butterknife:5.0.1' - compile 'de.devland.esperandro:esperandro-api:1.1.2' apt 'de.devland.esperandro:esperandro:1.1.2' - // compile 'com.f2prateek.dart:dart:1.1.0' - compile 'com.readystatesoftware.systembartint:systembartint:1.0.3' - compile 'de.greenrobot:eventbus:2.2.0' - + // compile 'de.greenrobot:eventbus:2.2.0' + compile 'com.netflix.rxjava:rxjava-android:0.19.0' // Handled by appcompat // compile 'com.google.android:support-v4:r7' - // And our unit testing needs some specific stuff (and specific stuff included again) androidTestCompile 'junit:junit:4.11+' androidTestCompile 'com.jayway.awaitility:awaitility:1.6.0' @@ -43,8 +39,7 @@ dependencies { android { compileSdkVersion 19 - buildToolsVersion "19.0.3" - + buildToolsVersion '19.1.0' sourceSets { main { manifest.srcFile 'AndroidManifest.xml' @@ -68,7 +63,6 @@ android { debug.setRoot('build-types/debug') release.setRoot('build-types/release') } - packagingOptions { exclude 'META-INF/LICENSE.txt' exclude 'LICENSE.txt' @@ -78,8 +72,20 @@ android { exclude 'META-INF/NOTICE' exclude 'META-INF/services/javax.annotation.processing.Processor' } - lintOptions { abortOnError false } + + /* + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + */ + + defaultConfig {} + productFlavors { + } + buildTypes { + } } \ No newline at end of file diff --git a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/BaseFragment.java b/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/BaseFragment.java index 85f341d..5b39dca 100644 --- a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/BaseFragment.java +++ b/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/BaseFragment.java @@ -17,7 +17,7 @@ public class BaseFragment extends Fragment { * @param context The {@link android.app.Activity} we are displaying in * @param view The {@link android.view.View} we need to calculate the offset for. */ - public static void setInsets(Activity context, View view) { + protected static void setInsets(Activity context, View view) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return; SystemBarTintManager tintManager = new SystemBarTintManager(context); SystemBarTintManager.SystemBarConfig config = tintManager.getConfig(); diff --git a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/ActivityDownloaderModule.java b/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/ActivityDownloaderModule.java index ddd351c..70ae79e 100644 --- a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/ActivityDownloaderModule.java +++ b/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/ActivityDownloaderModule.java @@ -3,15 +3,11 @@ package org.bspeice.minimalbible.activities.downloader; import org.bspeice.minimalbible.MinimalBible; import org.bspeice.minimalbible.activities.downloader.manager.BookDownloadManager; import org.bspeice.minimalbible.activities.downloader.manager.BookDownloadThread; -import org.bspeice.minimalbible.activities.downloader.manager.BookRefreshTask; -import org.bspeice.minimalbible.activities.downloader.manager.DownloadManager; -import org.bspeice.minimalbible.activities.downloader.manager.InstalledManager; import org.bspeice.minimalbible.activities.downloader.manager.RefreshManager; import dagger.Module; import dagger.Provides; import de.devland.esperandro.Esperandro; -import de.greenrobot.event.EventBus; /** * Module mappings for the classes under the Download Activity @@ -19,22 +15,14 @@ import de.greenrobot.event.EventBus; @Module( injects = { BookListFragment.class, - DownloadManager.class, - BookRefreshTask.class, BookItemHolder.class, BookDownloadManager.class, BookDownloadThread.class, - RefreshManager.class, - InstalledManager.class + RefreshManager.class } ) public class ActivityDownloaderModule { - @Provides - EventBus provideBus() { - return new EventBus(); - } - @Provides //@Singleton DownloadPrefs provideDownloadPrefs() { return Esperandro.getPreferences(DownloadPrefs.class, MinimalBible.getAppContext()); diff --git a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/BookItemHolder.java b/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/BookItemHolder.java index 24de2d1..ff38604 100644 --- a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/BookItemHolder.java +++ b/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/BookItemHolder.java @@ -11,17 +11,18 @@ import org.bspeice.minimalbible.MinimalBible; import org.bspeice.minimalbible.R; import org.bspeice.minimalbible.activities.downloader.manager.BookDownloadManager; import org.bspeice.minimalbible.activities.downloader.manager.DLProgressEvent; -import org.bspeice.minimalbible.activities.downloader.manager.DownloadManager; import org.bspeice.minimalbible.activities.downloader.manager.InstalledManager; import org.crosswire.jsword.book.Book; -import org.crosswire.jsword.book.BookException; -import org.crosswire.jsword.book.Books; import javax.inject.Inject; import butterknife.ButterKnife; import butterknife.InjectView; import butterknife.OnClick; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action1; +import rx.functions.Func1; /** * Created by bspeice on 5/20/14. @@ -36,11 +37,11 @@ public class BookItemHolder { @InjectView(R.id.download_ibtn_download) ImageButton isDownloaded; @InjectView(R.id.download_prg_download) ProgressWheel downloadProgress; - @Inject DownloadManager downloadManager; @Inject BookDownloadManager bookDownloadManager; @Inject InstalledManager installedManager; - Book b; + private final Book b; + private Subscription subscription; public BookItemHolder(View v, Book b) { ButterKnife.inject(this, v); @@ -57,7 +58,21 @@ public class BookItemHolder { } else if (installedManager.isInstalled(b)) { displayInstalled(); } - downloadManager.getDownloadBus().register(this); + //TODO: Refactor + subscription = bookDownloadManager.getDownloadEvents() + .observeOn(AndroidSchedulers.mainThread()) + .filter(new Func1() { + @Override + public Boolean call(DLProgressEvent event) { + return event.getB().getInitials().equals(b.getInitials()); + } + }) + .subscribe(new Action1() { + @Override + public void call(DLProgressEvent event) { + BookItemHolder.this.displayProgress((int) event.toCircular()); + } + }); } private void displayInstalled() { @@ -75,12 +90,6 @@ public class BookItemHolder { } } - public void onEventMainThread(DLProgressEvent event) { - if (event.getB().getOsisID().equals(b.getOsisID())) { - displayProgress((int) event.toCircular()); - } - } - /** * Display the current progress of this download * @param progress The progress out of 360 (degrees of a circle) @@ -119,6 +128,7 @@ public class BookItemHolder { downloadProgress.setProgress(progress); } else { // Download complete + subscription.unsubscribe(); RelativeLayout.LayoutParams acronymParams = (RelativeLayout.LayoutParams)acronym.getLayoutParams(); acronymParams.addRule(RelativeLayout.LEFT_OF, isDownloaded.getId()); @@ -134,6 +144,6 @@ public class BookItemHolder { } public void onScrollOffscreen() { - downloadManager.getDownloadBus().unregister(this); + subscription.unsubscribe(); } } diff --git a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/BookListAdapter.java b/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/BookListAdapter.java index 3f0a619..aac8f73 100644 --- a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/BookListAdapter.java +++ b/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/BookListAdapter.java @@ -15,9 +15,9 @@ import java.util.List; * Adapter to inflate list_download_items.xml */ public class BookListAdapter extends BaseAdapter implements AbsListView.RecyclerListener { - private List bookList; + private final List bookList; - private LayoutInflater inflater; + private final LayoutInflater inflater; public BookListAdapter(LayoutInflater inflater, List bookList) { this.bookList = bookList; diff --git a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/BookListFragment.java b/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/BookListFragment.java index 67b399c..90c875e 100644 --- a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/BookListFragment.java +++ b/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/BookListFragment.java @@ -5,7 +5,6 @@ import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.DialogInterface; import android.os.Bundle; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -15,22 +14,22 @@ import android.widget.Toast; import org.bspeice.minimalbible.MinimalBible; import org.bspeice.minimalbible.R; import org.bspeice.minimalbible.activities.BaseFragment; -import org.bspeice.minimalbible.activities.downloader.manager.DownloadManager; -import org.bspeice.minimalbible.activities.downloader.manager.EventBookList; import org.bspeice.minimalbible.activities.downloader.manager.RefreshManager; import org.crosswire.jsword.book.Book; import org.crosswire.jsword.book.BookCategory; import org.crosswire.jsword.book.BookComparators; -import org.crosswire.jsword.book.BookFilter; -import org.crosswire.jsword.book.FilterUtil; -import java.util.Collections; import java.util.List; import javax.inject.Inject; import butterknife.ButterKnife; import butterknife.InjectView; +import rx.Observable; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action1; +import rx.functions.Func1; +import rx.functions.Func2; /** * A placeholder fragment containing a simple view. @@ -48,9 +47,7 @@ public class BookListFragment extends BaseFragment { @InjectView(R.id.lst_download_available) ListView downloadsAvailable; - @Inject DownloadManager downloadManager; @Inject RefreshManager refreshManager; - @Inject DownloadPrefs downloadPrefs; private ProgressDialog refreshDialog; @@ -96,7 +93,7 @@ public class BookListFragment extends BaseFragment { * Trigger the functionality to display a list of modules. Prompts user if downloading * from the internet is allowable. */ - public void displayModules() { + private void displayModules() { boolean dialogDisplayed = downloadPrefs.hasShownDownloadDialog(); if (!dialogDisplayed) { @@ -118,52 +115,49 @@ public class BookListFragment extends BaseFragment { */ private void refreshModules() { // Check if the downloadManager has already refreshed everything - List bookList = refreshManager.getBookList(); - if (bookList == null) { + if (!refreshManager.isRefreshComplete()) { // downloadManager is in progress of refreshing - downloadManager.getDownloadBus().register(this); refreshDialog = new ProgressDialog(getActivity()); refreshDialog.setMessage("Refreshing available modules..."); refreshDialog.setCancelable(false); refreshDialog.show(); - } else { - displayBooks(bookList); } + + // Listen for the books! + refreshManager.getAvailableModulesFlattened() + .filter(new Func1() { + @Override + public Boolean call(Book book) { + return book.getBookCategory() == + BookCategory.fromString(BookListFragment.this.getArguments() + .getString(ARG_BOOK_CATEGORY)); + } + }) + // Repack all the books + .toSortedList(new Func2() { + @Override + public Integer call(Book book1, Book book2) { + return BookComparators.getInitialComparator().compare(book1, book2); + } + }) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1>() { + @Override + public void call(List books) { + downloadsAvailable.setAdapter(new BookListAdapter(inflater, books)); + if (BookListFragment.this.getActivity() != null) { + // On a screen rotate, getActivity() will be null. But, the activity will + // already have been set up correctly, so we don't need to worry about it. + // If not null, we need to set it up now. + setInsets(BookListFragment.this.getActivity(), downloadsAvailable); + } + if (refreshDialog != null) { + refreshDialog.cancel(); + } + } + }); } - /** - * Used by GreenRobot for notifying us that the book refresh is complete - */ - @SuppressWarnings("unused") - public void onEventMainThread(EventBookList event) { - if (refreshDialog != null) { - refreshDialog.cancel(); - } - displayBooks(event.getBookList()); - } - - /** - * Do the hard work of creating the Adapter and displaying books. - * @param bookList The (unfiltered) list of {link org.crosswire.jsword.Book}s to display - */ - public void displayBooks(List bookList) { - try { - // TODO: Should the filter be applied earlier in the process? - List displayList; - - BookCategory c = BookCategory.fromString(getArguments().getString(ARG_BOOK_CATEGORY)); - BookFilter f = FilterUtil.filterFromCategory(c); - displayList = FilterUtil.applyFilter(bookList, f); - Collections.sort(displayList, BookComparators.getInitialComparator()); - - downloadsAvailable.setAdapter(new BookListAdapter(inflater, displayList)); - setInsets(getActivity(), downloadsAvailable); - } catch (FilterUtil.InvalidFilterCategoryMappingException e) { - // To be honest, there should be no reason you end up here. - Log.e(TAG, e.getMessage()); - } - } - private class DownloadDialogListener implements DialogInterface.OnClickListener { @Override diff --git a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/DownloadActivity.java b/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/DownloadActivity.java index b00d505..423199d 100644 --- a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/DownloadActivity.java +++ b/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/DownloadActivity.java @@ -1,10 +1,5 @@ package org.bspeice.minimalbible.activities.downloader; -import org.bspeice.minimalbible.R; -import org.bspeice.minimalbible.activities.BaseActivity; -import org.bspeice.minimalbible.activities.BaseNavigationDrawerFragment; -import org.bspeice.minimalbible.activities.downloader.manager.DownloadManager; - import android.os.Bundle; import android.support.v4.app.FragmentManager; import android.support.v4.widget.DrawerLayout; @@ -12,6 +7,11 @@ import android.support.v7.app.ActionBar; import android.view.Menu; import android.view.MenuItem; +import org.bspeice.minimalbible.R; +import org.bspeice.minimalbible.activities.BaseActivity; +import org.bspeice.minimalbible.activities.BaseNavigationDrawerFragment; +import org.bspeice.minimalbible.activities.downloader.manager.DownloadManager; + public class DownloadActivity extends BaseActivity implements BaseNavigationDrawerFragment.NavigationDrawerCallbacks { diff --git a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/BookDownloadManager.java b/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/BookDownloadManager.java index 63c7b6b..663b558 100644 --- a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/BookDownloadManager.java +++ b/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/BookDownloadManager.java @@ -19,7 +19,7 @@ import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; -import de.greenrobot.event.EventBus; +import rx.subjects.PublishSubject; /** * Wrapper to convert JSword progress events to MinimalBible EventBus-based @@ -32,18 +32,18 @@ public class BookDownloadManager implements WorkListener, BooksListener { /** * Mapping of Job ID to the EventBus we should trigger progress on */ - private Map bookMappings; + private final Map bookMappings; /** * Cached copy of downloads in progress so views displaying this info can get it quickly. */ - private Map inProgressDownloads; + private final Map inProgressDownloads; + + private final PublishSubject downloadEvents = PublishSubject.create(); @Inject Provider dlThreadProvider; - @Inject DownloadManager downloadManager; - public BookDownloadManager() { bookMappings = new HashMap(); inProgressDownloads = new HashMap(); @@ -56,6 +56,7 @@ public class BookDownloadManager implements WorkListener, BooksListener { BookDownloadThread dlThread = dlThreadProvider.get(); dlThread.downloadBook(b); addJob(BookDownloadThread.getJobId(b), b); + downloadEvents.onNext(new DLProgressEvent(DLProgressEvent.PROGRESS_BEGINNING, b)); } public void addJob(String jobId, Book b) { @@ -66,20 +67,20 @@ public class BookDownloadManager implements WorkListener, BooksListener { public void workProgressed(WorkEvent ev) { Progress job = ev.getJob(); Log.d("BookDownloadManager", "Download in progress: " + job.getJobID() + " - " + job.getJobName() + " " + job.getWorkDone() + "/" + job.getTotalWork()); - EventBus downloadBus = downloadManager.getDownloadBus(); if (bookMappings.containsKey(job.getJobID())) { Book b = bookMappings.get(job.getJobID()); if (job.getWorkDone() == job.getTotalWork()) { // Download is complete inProgressDownloads.remove(bookMappings.get(job.getJobID())); - downloadBus.post(new DLProgressEvent(DLProgressEvent.PROGRESS_COMPLETE, b)); + bookMappings.remove(job.getJobID()); + downloadEvents.onNext(new DLProgressEvent(DLProgressEvent.PROGRESS_COMPLETE, b)); } else { // Track the ongoing download DLProgressEvent event = new DLProgressEvent(job.getWorkDone(), job.getTotalWork(), b); inProgressDownloads.put(b, event); - downloadBus.post(event); + downloadEvents.onNext(event); } } } @@ -97,6 +98,10 @@ public class BookDownloadManager implements WorkListener, BooksListener { } } + public PublishSubject getDownloadEvents() { + return downloadEvents; + } + @Override public void workStateChanged(WorkEvent ev) { Log.d("BookDownloadManager", ev.toString()); @@ -114,8 +119,7 @@ public class BookDownloadManager implements WorkListener, BooksListener { // Not sure why, but the inProgressDownloads might not have our book, // so we always trigger the PROGRESS_COMPLETE event. // TODO: Make sure all books get to the inProgressDownloads - downloadManager.getDownloadBus() - .post(new DLProgressEvent(DLProgressEvent.PROGRESS_COMPLETE, b)); + downloadEvents.onNext(new DLProgressEvent(DLProgressEvent.PROGRESS_COMPLETE, b)); } @Override diff --git a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/BookDownloadThread.java b/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/BookDownloadThread.java index 70edeec..477d61f 100644 --- a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/BookDownloadThread.java +++ b/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/BookDownloadThread.java @@ -9,6 +9,9 @@ import org.crosswire.jsword.book.install.Installer; import javax.inject.Inject; +import rx.functions.Action1; +import rx.schedulers.Schedulers; + /** * Thread that handles downloading a book */ @@ -18,8 +21,9 @@ public class BookDownloadThread { private final String TAG = "BookDownloadThread"; @Inject - DownloadManager downloadManager; - @Inject RefreshManager refreshManager; + BookDownloadManager bookDownloadManager; + @Inject + RefreshManager refreshManager; public BookDownloadThread() { MinimalBible.getApplication().inject(this); @@ -29,31 +33,32 @@ public class BookDownloadThread { // So, the JobManager can't be injected, but we'll make do // First, look up where the Book came from - final Installer i = refreshManager.installerFromBook(b); + refreshManager.installerFromBook(b) + .subscribeOn(Schedulers.io()) + .subscribe(new Action1() { + @Override + public void call(Installer installer) { + try { + installer.install(b); + } catch (InstallException e) { + Log.d(TAG, e.getMessage()); + } - final Thread worker = new Thread() { - @Override - public void run() { - try { - i.install(b); - } catch (InstallException e) { - Log.d(TAG, e.getMessage()); - } - } - }; - worker.start(); - // The worker automatically communicates with the JobManager for its progress. - - downloadManager.getDownloadBus().post(new DLProgressEvent(DLProgressEvent.PROGRESS_BEGINNING, b)); + bookDownloadManager.getDownloadEvents() + .onNext(new DLProgressEvent(DLProgressEvent.PROGRESS_BEGINNING, b)); + } + }); } /** * Build what the installer creates the job name as. * Likely prone to be brittle. * TODO: Make sure to test that this is an accurate job name + * * @param b The book to predict the download job name of * @return The name of the job that will/is download/ing this book */ + public static String getJobId(Book b) { return "INSTALL_BOOK-" + b.getInitials(); } diff --git a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/BookRefreshTask.java b/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/BookRefreshTask.java deleted file mode 100644 index 672d22a..0000000 --- a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/BookRefreshTask.java +++ /dev/null @@ -1,86 +0,0 @@ -package org.bspeice.minimalbible.activities.downloader.manager; - -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.os.AsyncTask; -import android.util.Log; - -import org.bspeice.minimalbible.MinimalBible; -import org.bspeice.minimalbible.activities.downloader.DownloadPrefs; -import org.crosswire.jsword.book.Book; -import org.crosswire.jsword.book.install.InstallException; -import org.crosswire.jsword.book.install.Installer; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.inject.Inject; - -public class BookRefreshTask extends AsyncTask> { - private static final String TAG = "EventBookRefreshTask"; - - // If last refresh was before the below, force an internet refresh - private final Long refreshAfter = System.currentTimeMillis() - 604800000L; // 1 Week in millis - - @Inject DownloadPrefs downloadPrefs; - - @Inject DownloadManager downloadManager; - @Inject InstalledManager installedManager; - - public BookRefreshTask() { - MinimalBible.getApplication().inject(this); - } - - @Override - protected List doInBackground(Installer... params) { - Map> bookList = new HashMap>(); - - int index = 0; - for (Installer i : params) { - if (doRefresh()) { - try { - i.reloadBookList(); - downloadPrefs.downloadRefreshedOn(System.currentTimeMillis()); - } catch (InstallException e) { - Log.e(TAG, - "Error downloading books from installer: " - + i.toString(), e); - } - } - bookList.put(i, i.getBooks()); - publishProgress(++index, params.length); - } - //TODO: Filter duplicates - - installedManager.initialize(); - EventBookList event = new EventBookList(bookList); - downloadManager.getDownloadBus().post(event); - - return event.getBookList(); - } - - private boolean doRefresh() { - // Check if we should refresh over the internet, or use the local copy - // TODO: Discover if we need to refresh over Internet, or use a cached - // copy - likely something time-based, also check network state. - // Fun fact - jSword handles the caching for us. - - return (isWifi() && downloadEnabled() && needsRefresh()); - } - - private boolean isWifi() { - ConnectivityManager mgr = (ConnectivityManager)MinimalBible.getAppContext().getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo networkInfo = mgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI); - return networkInfo.isConnected(); - } - - private boolean downloadEnabled() { - return downloadPrefs.hasEnabledDownload(); - } - - private boolean needsRefresh() { - return (downloadPrefs.downloadRefreshedOn() > refreshAfter); - } -} diff --git a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/DLProgressEvent.java b/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/DLProgressEvent.java index ae5f981..135998e 100644 --- a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/DLProgressEvent.java +++ b/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/DLProgressEvent.java @@ -6,8 +6,8 @@ import org.crosswire.jsword.book.Book; * Used for notifying that a book's download progress is ongoing */ public class DLProgressEvent { - private int progress; - private Book b; + private final int progress; + private final Book b; public static final int PROGRESS_COMPLETE = 100; public static final int PROGRESS_BEGINNING = 0; diff --git a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/DownloadManager.java b/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/DownloadManager.java index 15bd66f..5cdb098 100644 --- a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/DownloadManager.java +++ b/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/DownloadManager.java @@ -14,19 +14,12 @@ import java.util.Map; import javax.inject.Inject; import javax.inject.Singleton; -import de.greenrobot.event.EventBus; - // TODO: Listen to BookInstall events? @Singleton public class DownloadManager { private final String TAG = "DownloadManager"; - @Inject - protected EventBus downloadBus; - - - public static final BookCategory[] VALID_CATEGORIES = { BookCategory.BIBLE, BookCategory.COMMENTARY, BookCategory.DICTIONARY, BookCategory.MAPS }; @@ -34,8 +27,8 @@ public class DownloadManager { /** * Set up the DownloadManager, and notify jSword of where it should store files at */ + @Inject public DownloadManager() { - MinimalBible.getApplication().inject(this); setDownloadDir(); } @@ -47,15 +40,6 @@ public class DownloadManager { return new InstallManager().getInstallers(); } - /** - * Helper method to transform the installers map to an array - * @return Array with all available {@link org.crosswire.jsword.book.install.Installer} objects - */ - public Installer[] getInstallersArray() { - Map installers = getInstallers(); - return installers.values().toArray(new Installer[installers.size()]); - } - /** * Notify jSword that it needs to store files in the Android internal directory * NOTE: Android will uninstall these files if you uninstall MinimalBible. @@ -71,14 +55,4 @@ public class DownloadManager { SwordBookPath.setDownloadDir(new File(home)); Log.d(TAG, "Sword download path: " + SwordBookPath.getSwordDownloadDir()); } - - /** - * Get the current download bus - * Used to broker refresh events, and ongoing download events - */ - public EventBus getDownloadBus() { - return this.downloadBus; - } - - } diff --git a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/EventBookList.java b/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/EventBookList.java deleted file mode 100644 index 353788f..0000000 --- a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/EventBookList.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.bspeice.minimalbible.activities.downloader.manager; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.crosswire.jsword.book.Book; -import org.crosswire.jsword.book.install.Installer; - -/** - * POJO class for {@link de.greenrobot.event.EventBus} to broadcast whenever - * we've finished updating the book list. - */ -public class EventBookList { - - private Map> bookMapping; - - public EventBookList(Map> bookList) { - this.bookMapping = bookList; - } - - public Map> getInstallerMapping() { - return bookMapping; - } - - public List getBookList() { - List bookList = new ArrayList(); - for (Installer i: bookMapping.keySet()) { - bookList.addAll(bookMapping.get(i)); - } - return bookList; - } -} diff --git a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/InstalledManager.java b/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/InstalledManager.java index 02df1c1..505f00b 100644 --- a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/InstalledManager.java +++ b/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/InstalledManager.java @@ -19,10 +19,11 @@ import javax.inject.Singleton; @Singleton public class InstalledManager implements BooksListener { - @Inject DownloadManager downloadManager; - private Books installedBooks; private List installedBooksList; + private String TAG = "InstalledManager"; + + @Inject InstalledManager() {} /** * Register our manager to receive events on Book install @@ -30,6 +31,7 @@ public class InstalledManager implements BooksListener { * so we don't put it in the constructor. */ public void initialize() { + //TODO: Move this to a true async, rather than separate initialize() function installedBooks = Books.installed(); installedBooksList = installedBooks.getBooks(); installedBooks.addBooksListener(this); @@ -44,6 +46,7 @@ public class InstalledManager implements BooksListener { @Override public void bookAdded(BooksEvent booksEvent) { + Log.d(TAG, "Book added: " + booksEvent.getBook().toString()); Book b = booksEvent.getBook(); if (!installedBooksList.contains(b)) { installedBooksList.add(b); @@ -52,6 +55,7 @@ public class InstalledManager implements BooksListener { @Override public void bookRemoved(BooksEvent booksEvent) { + Log.d(TAG, "Book removed: " + booksEvent.getBook().toString()); Book b = booksEvent.getBook(); if (installedBooksList.contains(b)) { installedBooksList.remove(b); @@ -59,10 +63,19 @@ public class InstalledManager implements BooksListener { } public void removeBook(Book b) { - try { - installedBooks.removeBook(b); - } catch (BookException e) { - Log.e("InstalledManager", "Unable to remove book (already uninstalled?): " + e.getLocalizedMessage()); + if (installedBooks == null) { + initialize(); + } + // Not sure why we need to call this multiple times, but... + while (Books.installed().getBooks().contains(b)) { + try { + // This worked in the past, but isn't now... + // installedBooks.remove(b); + Book realBook = installedBooks.getBook(b.getInitials()); + b.getDriver().delete(realBook); + } catch (BookException e) { + Log.e("InstalledManager", "Unable to remove book (already uninstalled?): " + e.getLocalizedMessage()); + } } } } diff --git a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/RefreshManager.java b/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/RefreshManager.java index e37a548..bf8c197 100644 --- a/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/RefreshManager.java +++ b/MinimalBible/src/main/java/org/bspeice/minimalbible/activities/downloader/manager/RefreshManager.java @@ -8,11 +8,17 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import javax.inject.Inject; import javax.inject.Singleton; -import de.greenrobot.event.EventBus; +import rx.Observable; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.functions.Func1; +import rx.functions.Func2; +import rx.schedulers.Schedulers; /** * Handle refreshing the list of books available as needed @@ -21,35 +27,74 @@ import de.greenrobot.event.EventBus; public class RefreshManager { @Inject DownloadManager downloadManager; + @Inject InstalledManager installedManager; /** * Cached copy of modules that are available so we don't refresh for everyone who requests it. */ - private Map> availableModules; + private Observable>> availableModules; + private final AtomicBoolean refreshComplete = new AtomicBoolean(); public RefreshManager() { MinimalBible.getApplication().inject(this); - availableModules = new HashMap>(); refreshModules(); } /** * Do the work of kicking off the AsyncTask to refresh books, and make sure we know * when it's done. + * TODO: Should I have a better way of scheduling than Schedulers.io()? */ private void refreshModules() { - EventBus refreshBus = downloadManager.getDownloadBus(); - refreshBus.register(this); - new BookRefreshTask().execute(downloadManager.getInstallersArray()); + if (availableModules == null) { + availableModules = Observable.from(downloadManager.getInstallers().values()) + .map(new Func1>>() { + @Override + public Map> call(Installer installer) { + Map> map = new HashMap>(); + map.put(installer, installer.getBooks()); + return map; + } + }).subscribeOn(Schedulers.io()) + .cache(); + + // Set refresh complete when it is. + availableModules.observeOn(Schedulers.io()) + .subscribe(new Action1>>() { + @Override + public void call(Map> onNext) {} + }, new Action1() { + @Override + public void call(Throwable onError) {} + }, new Action0() { + @Override + public void call() { + refreshComplete.set(true); + } + }); + } } - /** - * When book refresh is done, cache the list so we can give that to someone else - * @param event A POJO wrapper around the Book list - */ - @SuppressWarnings("unused") - public void onEvent(EventBookList event) { - this.availableModules = event.getInstallerMapping(); + public Observable>> getAvailableModules() { + return availableModules; + } + + public Observable getAvailableModulesFlattened() { + return availableModules + // First flatten the Map to its lists + .flatMap(new Func1>, Observable>>() { + @Override + public Observable> call(Map> books) { + return Observable.from(books.values()); + } + }) + // Then flatten the lists + .flatMap(new Func1, Observable>() { + @Override + public Observable call(List t1) { + return Observable.from(t1); + } + }); } /** @@ -57,15 +102,18 @@ public class RefreshManager { * @return The cached book list, or null */ public List getBookList() { - if (availableModules.values().size() == 0) { - return null; - } else { - List bookList = new ArrayList(); - for (List l : availableModules.values()) { - bookList.addAll(l); + List availableList = new ArrayList(); + availableModules.reduce(availableList, + new Func2, Map>, List>() { + @Override + public List call(List books, Map> installerListMap) { + for (List l : installerListMap.values()) { + books.addAll(l); + } + return books; } - return bookList; - } + }); + return availableList; } /** @@ -73,12 +121,28 @@ public class RefreshManager { * @param b The book to search for * @return The Installer that should be used for this book. */ - public Installer installerFromBook(Book b) { - for (Map.Entry> entry : availableModules.entrySet()) { - if (entry.getValue().contains(b)) { - return entry.getKey(); + public Observable installerFromBook(final Book b) { + return availableModules.filter(new Func1>, Boolean>() { + @Override + public Boolean call(Map> installerListMap) { + for (List element : installerListMap.values()) { + if (element.contains(b)) { + return true; + } + } + return false; } - } - return null; + }) + .first() + .map(new Func1>, Installer>() { + @Override + public Installer call(Map> element) { + return element.entrySet().iterator().next().getKey(); + } + }); + } + + public boolean isRefreshComplete() { + return refreshComplete.get(); } } diff --git a/MinimalBible/src/test/java/org/bspeice/minimalbible/test/DownloadActivityTest.java b/MinimalBible/src/test/java/org/bspeice/minimalbible/test/DownloadActivityTest.java index 76c4e05..990cd11 100644 --- a/MinimalBible/src/test/java/org/bspeice/minimalbible/test/DownloadActivityTest.java +++ b/MinimalBible/src/test/java/org/bspeice/minimalbible/test/DownloadActivityTest.java @@ -1,10 +1,11 @@ package org.bspeice.minimalbible.test; import android.test.InstrumentationTestCase; +import android.util.Log; import org.bspeice.minimalbible.MinimalBible; import org.bspeice.minimalbible.MinimalBibleModules; -import org.bspeice.minimalbible.activities.downloader.manager.BookDownloadThread; +import org.bspeice.minimalbible.activities.downloader.manager.BookDownloadManager; import org.bspeice.minimalbible.activities.downloader.manager.DLProgressEvent; import org.bspeice.minimalbible.activities.downloader.manager.DownloadManager; import org.bspeice.minimalbible.activities.downloader.manager.InstalledManager; @@ -12,20 +13,25 @@ import org.bspeice.minimalbible.activities.downloader.manager.RefreshManager; import org.crosswire.jsword.book.Book; import org.crosswire.jsword.book.BookException; import org.crosswire.jsword.book.Books; +import org.crosswire.jsword.book.install.InstallException; import org.crosswire.jsword.book.install.Installer; import org.crosswire.jsword.passage.NoSuchKeyException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import javax.inject.Inject; -import javax.inject.Provider; import dagger.Module; import dagger.ObjectGraph; +import rx.Observable; -import static com.jayway.awaitility.Awaitility.*; +import static com.jayway.awaitility.Awaitility.await; /** * Tests for the Download activity @@ -38,8 +44,8 @@ public class DownloadActivityTest extends InstrumentationTestCase { @Inject DownloadManager dm; @Inject InstalledManager im; - @Inject Provider bookDownloadThreadProvider; @Inject RefreshManager rm; + @Inject BookDownloadManager bdm; public void setUp() { MinimalBible application = MinimalBible.getApplication(); @@ -52,40 +58,6 @@ public class DownloadActivityTest extends InstrumentationTestCase { assertEquals(true, true); } - /** - * When we start a download, make sure a progress event of 0 is triggered. - */ - public void testInitialProgressEventOnDownload() throws InterruptedException { - final CountDownLatch signal = new CountDownLatch(1); - - // Need to make sure we've refreshed the refreshmanager first - Installer i = (Installer) dm.getInstallers().values().toArray()[0]; - final Book testBook = i.getBooks().get(0); - await().atMost(30, TimeUnit.SECONDS).until(new Callable() { - @Override - public Boolean call() throws Exception { - return rm.installerFromBook(testBook) != null; - } - }); - - // And wait for the actual download - dm.getDownloadBus().register(new Object() { - public void onEvent(DLProgressEvent event) { - if (event.getProgress() == 0) { - signal.countDown(); - } - } - }); - - BookDownloadThread thread = bookDownloadThreadProvider.get(); - thread.downloadBook(testBook); - - signal.await(10, TimeUnit.SECONDS); - if (signal.getCount() != 0) { - fail("Event did not trigger!"); - } - } - /** * Test that we can successfully download and remove a book */ @@ -93,12 +65,14 @@ public class DownloadActivityTest extends InstrumentationTestCase { // Install a book Installer i = (Installer) dm.getInstallers().values().toArray()[0]; final Book testBook = i.getBooks().get(0); - await().atMost(30, TimeUnit.SECONDS).until(new Callable() { - @Override - public Boolean call() throws Exception { - return Books.installed().getBooks().contains(testBook); - } - }); + bdm.installBook(testBook); + await().atMost(30, TimeUnit.SECONDS) + .until(new Callable() { + @Override + public Boolean call() throws Exception { + return Books.installed().getBooks().contains(testBook); + } + }); // Validate that we can actually do something with the book // TODO: Validate that the book exists on the filesystem too @@ -113,7 +87,13 @@ public class DownloadActivityTest extends InstrumentationTestCase { // Remove the book and make sure it's gone // TODO: Validate that the book is off the filesystem im.removeBook(testBook); - assertFalse(Books.installed().getBooks().contains(testBook)); + await().atMost(10, TimeUnit.SECONDS) + .until(new Callable() { + @Override + public Boolean call() throws Exception { + return !Books.installed().getBooks().contains(testBook); + } + }); } } diff --git a/appcompat_v7/build.gradle b/appcompat_v7/build.gradle index 2c144a5..21b91c2 100644 --- a/appcompat_v7/build.gradle +++ b/appcompat_v7/build.gradle @@ -6,7 +6,7 @@ dependencies { android { compileSdkVersion 19 - buildToolsVersion "19.0.3" + buildToolsVersion "19.1.0" sourceSets { main { diff --git a/build.gradle b/build.gradle index fdf10eb..8ebc4db 100644 --- a/build.gradle +++ b/build.gradle @@ -4,6 +4,6 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:0.9.+' + classpath 'com.android.tools.build:gradle:0.11.+' } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5399b01..de04f62 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon May 05 23:06:48 EDT 2014 +#Tue Jun 10 19:26:46 EDT 2014 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=http\://services.gradle.org/distributions/gradle-1.11-all.zip +distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-all.zip