package com.quran.labs.androidquran.presenter; import android.Manifest; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.ParcelFileDescriptor; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import android.support.v4.app.ActivityCompat; import com.quran.labs.androidquran.QuranImportActivity; import com.quran.labs.androidquran.dao.BookmarkData; import com.quran.labs.androidquran.model.bookmark.BookmarkImportExportModel; import com.quran.labs.androidquran.model.bookmark.BookmarkModel; import com.quran.labs.androidquran.service.util.PermissionUtil; import com.quran.labs.androidquran.util.QuranSettings; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.InputStream; import java.util.concurrent.Callable; import javax.inject.Inject; import javax.inject.Singleton; import io.reactivex.Maybe; import io.reactivex.MaybeSource; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.functions.Function; import io.reactivex.observers.DisposableMaybeObserver; import io.reactivex.schedulers.Schedulers; import okio.BufferedSource; import okio.Okio; @Singleton public class QuranImportPresenter implements Presenter<QuranImportActivity> { private static final int REQUEST_WRITE_TO_SDCARD_PERMISSIONS = 1; private final Context mAppContext; private final BookmarkModel mBookmarkModel; private final BookmarkImportExportModel mBookmarkImportExportModel; private boolean mRequestingPermissions; private Observable<Boolean> mImportObservable; private QuranImportActivity mCurrentActivity; @Inject QuranImportPresenter(Context appContext, BookmarkImportExportModel model, BookmarkModel bookmarkModel) { mAppContext = appContext; mBookmarkModel = bookmarkModel; mBookmarkImportExportModel = model; } private void handleIntent(Intent intent) { mRequestingPermissions = false; if (mImportObservable == null) { Uri uri = intent.getData(); if (uri == null && intent.getExtras() != null) { uri = (Uri) intent.getExtras().get(Intent.EXTRA_STREAM); } if (uri != null) { parseIntentUri(uri); } else if (mCurrentActivity != null) { mCurrentActivity.showError(); } } else { subscribeToImportData(); } } public void importData(final BookmarkData data) { mImportObservable = mBookmarkModel.importBookmarksObservable(data); subscribeToImportData(); } public void onPermissionsResult(int requestCode, @NonNull int[] grantResults) { if (requestCode == REQUEST_WRITE_TO_SDCARD_PERMISSIONS) { mRequestingPermissions = false; if (mCurrentActivity != null) { if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { handleIntent(mCurrentActivity.getIntent()); } else { mCurrentActivity.showPermissionsError(); } } } } private void subscribeToImportData() { mImportObservable .observeOn(AndroidSchedulers.mainThread()) .subscribe(aBoolean -> { if (mCurrentActivity != null) { mCurrentActivity.showImportComplete(); mImportObservable = null; } }); } private void parseIntentUri(final Uri uri) { getBookmarkDataObservable(parseUri(uri)) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new DisposableMaybeObserver<BookmarkData>() { @Override public void onSuccess(BookmarkData bookmarkData) { if (mCurrentActivity != null) { mCurrentActivity.showImportConfirmationDialog(bookmarkData); } } @Override public void onError(Throwable e) { if (mCurrentActivity != null) { handleExternalStorageFile(uri); } } @Override public void onComplete() { if (mCurrentActivity != null) { handleExternalStorageFile(uri); } } }); } private void handleExternalStorageFile(Uri uri) { if (PermissionUtil.haveWriteExternalStoragePermission(mAppContext)) { handleExternalStorageFileInternal(uri); } else if (mCurrentActivity != null) { mRequestingPermissions = true; if (PermissionUtil.canRequestWriteExternalStoragePermission(mCurrentActivity)) { QuranSettings.getInstance(mAppContext).setSdcardPermissionsDialogPresented(); ActivityCompat.requestPermissions(mCurrentActivity, new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE }, REQUEST_WRITE_TO_SDCARD_PERMISSIONS); } else { mCurrentActivity.showPermissionsError(); } } } private void handleExternalStorageFileInternal(Uri uri) { getBookmarkDataObservable(parseExternalFile(uri)) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new DisposableMaybeObserver<BookmarkData>() { @Override public void onSuccess(BookmarkData bookmarkData) { if (mCurrentActivity != null) { mCurrentActivity.showImportConfirmationDialog(bookmarkData); } } @Override public void onError(Throwable e) { if (mCurrentActivity != null) { mCurrentActivity.showError(); } } @Override public void onComplete() { if (mCurrentActivity != null) { mCurrentActivity.showError(); } } }); } private Maybe<BookmarkData> getBookmarkDataObservable(Maybe<BufferedSource> source) { return source .flatMap(new Function<BufferedSource, MaybeSource<BookmarkData>>() { @Override public MaybeSource<BookmarkData> apply(BufferedSource bufferedSource) throws Exception { return mBookmarkImportExportModel.readBookmarks(bufferedSource).toMaybe(); } }) .subscribeOn(Schedulers.io()); } @NonNull @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) Maybe<BufferedSource> parseUri(final Uri uri) { return Maybe.defer(new Callable<MaybeSource<BufferedSource>>() { @Override public MaybeSource<BufferedSource> call() throws Exception { ParcelFileDescriptor pfd = mAppContext.getContentResolver().openFileDescriptor(uri, "r"); if (pfd != null) { FileDescriptor fd = pfd.getFileDescriptor(); return Maybe.just(Okio.buffer(Okio.source(new FileInputStream(fd)))); } return Maybe.empty(); } }); } @NonNull @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) Maybe<BufferedSource> parseExternalFile(final Uri uri) { return Maybe.defer(new Callable<MaybeSource<BufferedSource>>() { @Override public MaybeSource<BufferedSource> call() throws Exception { InputStream stream = mAppContext.getContentResolver().openInputStream(uri); if (stream != null) { return Maybe.just(Okio.buffer(Okio.source(stream))); } return Maybe.empty(); } }); } @Override public void bind(QuranImportActivity activity) { mCurrentActivity = activity; if (!activity.isShowingDialog() && !mRequestingPermissions) { handleIntent(activity.getIntent()); } } @Override public void unbind(QuranImportActivity activity) { if (activity == mCurrentActivity) { mCurrentActivity = null; } } }