package co.smartreceipts.android.imports; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import com.google.common.base.Preconditions; import java.io.FileNotFoundException; import co.smartreceipts.android.analytics.Analytics; import co.smartreceipts.android.analytics.events.ErrorEvent; import co.smartreceipts.android.model.Trip; import co.smartreceipts.android.ocr.OcrManager; import co.smartreceipts.android.persistence.PersistenceManager; import co.smartreceipts.android.utils.log.Logger; import io.reactivex.Maybe; import io.reactivex.Observable; import io.reactivex.Scheduler; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.observers.DisposableObserver; import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.ReplaySubject; import io.reactivex.subjects.Subject; public class ActivityFileResultImporter { private static final String TAG = ActivityFileResultImporter.class.getSimpleName(); private final Context context; private final FileImportProcessorFactory factory; private final Analytics analytics; private final ActivityImporterHeadlessFragment headlessFragment; private final OcrManager ocrManager; private final Scheduler subscribeOnScheduler; private final Scheduler observeOnScheduler; public ActivityFileResultImporter(@NonNull Context context, @NonNull FragmentManager fragmentManager, @NonNull Trip trip, @NonNull PersistenceManager persistenceManager, @NonNull Analytics analytics, @NonNull OcrManager ocrManager) { this(context, fragmentManager, new FileImportProcessorFactory(context, trip, persistenceManager), analytics, ocrManager, Schedulers.io(), AndroidSchedulers.mainThread()); } public ActivityFileResultImporter(@NonNull Context context, @NonNull FragmentManager fragmentManager, @NonNull FileImportProcessorFactory factory, @NonNull Analytics analytics, @NonNull OcrManager ocrManager, @NonNull Scheduler subscribeOnScheduler, @NonNull Scheduler observeOnScheduler) { this.context = Preconditions.checkNotNull(context.getApplicationContext()); this.factory = Preconditions.checkNotNull(factory); this.analytics = Preconditions.checkNotNull(analytics); this.ocrManager = Preconditions.checkNotNull(ocrManager); this.subscribeOnScheduler = Preconditions.checkNotNull(subscribeOnScheduler); this.observeOnScheduler = Preconditions.checkNotNull(observeOnScheduler); Preconditions.checkNotNull(fragmentManager); ActivityImporterHeadlessFragment headlessFragment = (ActivityImporterHeadlessFragment) fragmentManager.findFragmentByTag(TAG); if (headlessFragment == null) { headlessFragment = new ActivityImporterHeadlessFragment(); fragmentManager.beginTransaction().add(headlessFragment, TAG).commit(); } this.headlessFragment = headlessFragment; if (this.headlessFragment.importSubject == null) { headlessFragment.importSubject = ReplaySubject.create(1); } } public void onActivityResult(final int requestCode, final int resultCode, @Nullable Intent data, @Nullable final Uri proposedImageSaveLocation) { Logger.info(this, "Performing import of onActivityResult data: {}", data); if (headlessFragment.localDisposable != null) { Logger.warn(this, "Clearing cached local subscription, a previous request was never fully completed"); headlessFragment.localDisposable.dispose(); headlessFragment.localDisposable = null; } headlessFragment.localDisposable = getSaveLocation(requestCode, resultCode, data, proposedImageSaveLocation) .subscribeOn(subscribeOnScheduler) .flatMapSingleElement(uri -> factory.get(requestCode).process(uri)) .flatMapObservable(file -> ocrManager.scan(file) .map(ocrResponse -> new ActivityFileResultImporterResponse(file, ocrResponse, requestCode, resultCode))) .doOnError(throwable -> { Logger.error(ActivityFileResultImporter.this, "Failed to save import result", throwable); analytics.record(new ErrorEvent(ActivityFileResultImporter.this, throwable)); }) .observeOn(observeOnScheduler) .subscribeWith(new DisposableObserver<ActivityFileResultImporterResponse>() { @Override public void onNext(ActivityFileResultImporterResponse activityFileResultImporterResponse) { headlessFragment.importSubject.onNext(activityFileResultImporterResponse); } @Override public void onError(Throwable e) { headlessFragment.importSubject.onError(e); } @Override public void onComplete() { headlessFragment.importSubject.onComplete(); } }); // TODO: 12.04.2017 !!! check dispose(). not sure if it works correctly } public Observable<ActivityFileResultImporterResponse> getResultStream() { return headlessFragment.importSubject; } public void dispose() { if (headlessFragment.localDisposable != null) { headlessFragment.localDisposable.dispose(); headlessFragment.localDisposable = null; } if (headlessFragment.importSubject != null) { this.headlessFragment.importSubject = ReplaySubject.create(1); } } public static final class ActivityImporterHeadlessFragment extends Fragment { private Subject<ActivityFileResultImporterResponse> importSubject = ReplaySubject.create(1); private Disposable localDisposable; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); } } private Maybe<Uri> getSaveLocation(final int requestCode, final int resultCode, @Nullable final Intent data, @Nullable final Uri proposedImageSaveLocation) { return Maybe.create(emitter -> { if (resultCode == Activity.RESULT_OK) { if ((data == null || data.getData() == null) && proposedImageSaveLocation == null) { emitter.onError(new FileNotFoundException("Unknown intent data and proposed save location for request " + requestCode + " with result " + resultCode)); } else { final Uri uri; if (data != null && data.getData() != null) { uri = data.getData(); } else { uri = proposedImageSaveLocation; } if (uri == null) { emitter.onError(new FileNotFoundException("Null Uri for request " + requestCode + " with result " + resultCode)); } else { Logger.info(ActivityFileResultImporter.this, "Image save location determined as {}", uri); emitter.onSuccess(uri); } } } else { Logger.warn(ActivityFileResultImporter.this, "Unknown activity result code (likely user cancelled): {} ", resultCode); emitter.onComplete(); } }); } }