package com.quran.labs.androidquran.ui.fragment;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceGroup;
import android.support.v7.app.AlertDialog;
import android.text.TextUtils;
import android.widget.Toast;
import com.crashlytics.android.answers.Answers;
import com.crashlytics.android.answers.CustomEvent;
import com.quran.labs.androidquran.BuildConfig;
import com.quran.labs.androidquran.QuranAdvancedPreferenceActivity;
import com.quran.labs.androidquran.QuranApplication;
import com.quran.labs.androidquran.QuranImportActivity;
import com.quran.labs.androidquran.R;
import com.quran.labs.androidquran.data.Constants;
import com.quran.labs.androidquran.model.bookmark.BookmarkImportExportModel;
import com.quran.labs.androidquran.service.util.PermissionUtil;
import com.quran.labs.androidquran.ui.preference.DataListPreference;
import com.quran.labs.androidquran.util.QuranFileUtils;
import com.quran.labs.androidquran.util.QuranSettings;
import com.quran.labs.androidquran.util.QuranUtils;
import com.quran.labs.androidquran.util.RecordingLogTree;
import com.quran.labs.androidquran.util.StorageUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javax.inject.Inject;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.observers.DisposableMaybeObserver;
import io.reactivex.observers.DisposableSingleObserver;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
public class QuranAdvancedSettingsFragment extends PreferenceFragment {
private static final int REQUEST_CODE_IMPORT = 1;
private DataListPreference listStoragePref;
private MoveFilesAsyncTask noveFilesTask;
private List<StorageUtils.Storage> storageList;
private LoadStorageOptionsTask loadStorageOptionsTask;
private int appSize;
private boolean isPaused;
private String internalSdcardLocation;
private AlertDialog dialog;
private Context appContext;
private Disposable exportSubscription = null;
private Disposable logsSubscription;
@Inject BookmarkImportExportModel bookmarkImportExportModel;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.quran_advanced_preferences);
final Context context = getActivity();
appContext = context.getApplicationContext();
// field injection
((QuranApplication) appContext).getApplicationComponent().inject(this);
final Preference logsPref = findPreference(Constants.PREF_LOGS);
if (BuildConfig.DEBUG || "beta".equals(BuildConfig.BUILD_TYPE)) {
logsPref.setOnPreferenceClickListener(preference -> {
if (logsSubscription == null) {
logsSubscription = Observable.fromIterable(Timber.forest())
.filter(tree -> tree instanceof RecordingLogTree)
.firstElement()
.map(tree -> ((RecordingLogTree) tree).getLogs())
.map(logs -> QuranUtils.getDebugInfo(appContext) + "\n\n" + logs)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableMaybeObserver<String>() {
@Override
public void onSuccess(String logs) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("message/rfc822");
intent.putExtra(Intent.EXTRA_EMAIL,
new String[]{ appContext.getString(R.string.logs_email) });
intent.putExtra(Intent.EXTRA_TEXT, logs);
intent.putExtra(Intent.EXTRA_SUBJECT, "Logs");
startActivity(Intent.createChooser(intent,
appContext.getString(R.string.prefs_send_logs_title)));
logsSubscription = null;
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
}
return true;
});
} else {
removeAdvancePreference(logsPref);
}
final Preference importPref = findPreference(Constants.PREF_IMPORT);
importPref.setOnPreferenceClickListener(preference -> {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
String[] mimeTypes = new String[]{ "application/*", "text/*" };
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
}
startActivityForResult(intent, REQUEST_CODE_IMPORT);
return true;
});
final Preference exportPref = findPreference(Constants.PREF_EXPORT);
exportPref.setOnPreferenceClickListener(preference -> {
if (exportSubscription == null) {
exportSubscription = bookmarkImportExportModel.exportBookmarksObservable()
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<Uri>() {
@Override
public void onSuccess(Uri uri) {
Answers.getInstance().logCustom(new CustomEvent("exportData"));
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("application/json");
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
List<ResolveInfo> intents = appContext.getPackageManager()
.queryIntentActivities(shareIntent, 0);
if (intents.size() > 1) {
// if only one, then that is likely Quran for Android itself, so don't show
// the chooser since it doesn't really make sense.
context.startActivity(Intent.createChooser(shareIntent,
context.getString(R.string.prefs_export_title)));
} else {
File exportedPath = new File(appContext.getExternalFilesDir(null), "backups");
String exported = appContext.getString(
R.string.exported_data, exportedPath.toString());
Toast.makeText(appContext, exported, Toast.LENGTH_LONG).show();
}
}
@Override
public void onError(Throwable e) {
exportSubscription = null;
if (isAdded()) {
Toast.makeText(context, R.string.export_data_error, Toast.LENGTH_LONG).show();
}
}
});
}
return true;
});
internalSdcardLocation =
Environment.getExternalStorageDirectory().getAbsolutePath();
listStoragePref = (DataListPreference) findPreference(getString(R.string.prefs_app_location));
listStoragePref.setEnabled(false);
try {
storageList = StorageUtils.getAllStorageLocations(context.getApplicationContext());
} catch (Exception e) {
Timber.d(e, "Exception while trying to get storage locations");
storageList = new ArrayList<>();
}
// Hide app location pref if there is no storage option
// except for the normal Environment.getExternalStorageDirectory
if (storageList == null || storageList.size() <= 1) {
Timber.d("removing advanced settings from preferences");
hideStorageListPref();
} else {
loadStorageOptionsTask = new LoadStorageOptionsTask(context);
loadStorageOptionsTask.execute();
}
}
@Override
public void onDestroy() {
if (exportSubscription != null) {
exportSubscription.dispose();
}
if (logsSubscription != null) {
logsSubscription.dispose();
}
if (dialog != null) {
dialog.dismiss();
}
super.onDestroy();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_IMPORT && resultCode == Activity.RESULT_OK) {
Activity activity = getActivity();
if (activity != null) {
Intent intent = new Intent(activity, QuranImportActivity.class);
intent.setData(data.getData());
startActivity(intent);
}
}
}
private void removeAdvancePreference(Preference preference) {
// these null checks are to fix a crash due to an NPE on 4.4.4
if (preference != null) {
PreferenceGroup group =
(PreferenceGroup) findPreference(Constants.PREF_QURAN_SETTINGS);
if (group != null) {
group.removePreference(preference);
}
}
}
private void hideStorageListPref() {
removeAdvancePreference(listStoragePref);
}
private void loadStorageOptions(Context context) {
try {
if (appSize == -1) {
// sdcard is not mounted...
hideStorageListPref();
return;
}
listStoragePref.setLabelsAndSummaries(context, appSize, storageList);
final HashMap<String, StorageUtils.Storage> storageMap =
new HashMap<>(storageList.size());
for (StorageUtils.Storage storage : storageList) {
storageMap.put(storage.getMountPoint(), storage);
}
listStoragePref
.setOnPreferenceChangeListener((preference, newValue) -> {
final Context context1 = getActivity();
final QuranSettings settings = QuranSettings.getInstance(context1);
if (TextUtils.isEmpty(settings.getAppCustomLocation()) &&
Environment.getExternalStorageDirectory().equals(newValue)) {
// do nothing since we're moving from empty settings to
// the default sdcard setting, which are the same, but write it.
return false;
}
// this is called right before the preference is saved
String newLocation = (String) newValue;
StorageUtils.Storage destStorage = storageMap.get(newLocation);
String current = settings.getAppCustomLocation();
if (appSize < destStorage.getFreeSpace()) {
if (current == null || !current.equals(newLocation)) {
if (destStorage.doesRequirePermission()) {
if (!PermissionUtil.haveWriteExternalStoragePermission(context1)) {
requestExternalStoragePermission(newLocation);
return false;
}
// we have the permission, so fall through and handle the move
}
handleMove(newLocation);
}
} else {
Toast.makeText(context1,
getString(
R.string.prefs_no_enough_space_to_move_files),
Toast.LENGTH_LONG).show();
}
// this says, "don't write the preference"
return false;
});
listStoragePref.setEnabled(true);
} catch (Exception e) {
Timber.e(e, "error loading storage options");
hideStorageListPref();
}
}
private void requestExternalStoragePermission(String newLocation) {
Activity activity = getActivity();
if (activity instanceof QuranAdvancedPreferenceActivity) {
((QuranAdvancedPreferenceActivity) activity)
.requestWriteExternalSdcardPermission(newLocation);
}
}
private void handleMove(String newLocation) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT ||
newLocation.equals(internalSdcardLocation)) {
moveFiles(newLocation);
} else {
showKitKatConfirmation(newLocation);
}
}
private void showKitKatConfirmation(final String newLocation) {
final Context context = getActivity();
final AlertDialog.Builder b = new AlertDialog.Builder(context)
.setTitle(R.string.warning)
.setMessage(R.string.kitkat_external_message)
.setPositiveButton(R.string.dialog_ok, (currentDialog, which) -> {
moveFiles(newLocation);
currentDialog.dismiss();
QuranAdvancedSettingsFragment.this.dialog = null;
})
.setNegativeButton(R.string.cancel, (currentDialog, which) -> {
currentDialog.dismiss();
QuranAdvancedSettingsFragment.this.dialog = null;
});
dialog = b.create();
dialog.show();
}
public void moveFiles(String newLocation) {
noveFilesTask = new MoveFilesAsyncTask(getActivity(), newLocation);
noveFilesTask.execute();
}
@Override
public void onResume() {
super.onResume();
isPaused = false;
}
@Override
public void onPause() {
isPaused = true;
super.onPause();
}
private class MoveFilesAsyncTask extends AsyncTask<Void, Void, Boolean> {
private String newLocation;
private ProgressDialog dialog;
private Context appContext;
private MoveFilesAsyncTask(Context context, String newLocation) {
this.newLocation = newLocation;
this.appContext = context.getApplicationContext();
}
@Override
protected void onPreExecute() {
dialog = new ProgressDialog(getActivity());
dialog.setMessage(appContext.getString(R.string.prefs_copying_app_files));
dialog.setCancelable(false);
dialog.show();
}
@Override
protected Boolean doInBackground(Void... voids) {
return QuranFileUtils.moveAppFiles(appContext, newLocation);
}
@Override
protected void onPostExecute(Boolean result) {
if (!isPaused) {
dialog.dismiss();
if (result) {
QuranSettings.getInstance(appContext).setAppCustomLocation(newLocation);
if (listStoragePref != null) {
listStoragePref.setValue(newLocation);
}
} else {
Toast.makeText(appContext,
getString(R.string.prefs_err_moving_app_files),
Toast.LENGTH_LONG).show();
}
dialog = null;
noveFilesTask = null;
}
}
}
private class LoadStorageOptionsTask extends AsyncTask<Void, Void, Void> {
private Context appContext;
LoadStorageOptionsTask(Context context) {
this.appContext = context.getApplicationContext();
}
@Override
protected void onPreExecute() {
listStoragePref.setSummary(R.string.prefs_calculating_app_size);
}
@Override
protected Void doInBackground(Void... voids) {
appSize = QuranFileUtils.getAppUsedSpace(appContext);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
if (!isPaused) {
loadStorageOptions(appContext);
loadStorageOptionsTask = null;
listStoragePref.setSummary(R.string.prefs_app_location_summary);
}
}
}
}