package com.battlelancer.seriesguide.dataliberation; import android.Manifest; import android.annotation.TargetApi; import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.design.widget.Snackbar; import android.support.v4.app.Fragment; import android.support.v4.content.ContextCompat; import android.support.v7.widget.SwitchCompat; import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.ProgressBar; import android.widget.TextView; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.Unbinder; import com.battlelancer.seriesguide.R; import com.battlelancer.seriesguide.interfaces.OnTaskFinishedListener; import com.battlelancer.seriesguide.interfaces.OnTaskProgressListener; import com.battlelancer.seriesguide.settings.AdvancedSettings; import com.battlelancer.seriesguide.settings.BackupSettings; import com.battlelancer.seriesguide.util.Utils; import com.uwetrottmann.androidutils.AndroidUtils; import timber.log.Timber; /** * Configuration of auto backup, the backup files and restoring the last auto backup. */ public class AutoBackupFragment extends Fragment implements OnTaskFinishedListener, OnTaskProgressListener { private static final int REQUEST_CODE_ENABLE_AUTO_BACKUP = 1; private static final int REQUEST_CODE_IMPORT_AUTOBACKUP = 2; private static final int REQUEST_CODE_SHOWS_EXPORT_URI = 3; private static final int REQUEST_CODE_LISTS_EXPORT_URI = 4; private static final int REQUEST_CODE_MOVIES_EXPORT_URI = 5; private static final int PATH_TEXT_APPEARANCE = R.style.TextAppearance_Body_Dim; @BindView(R.id.switchAutoBackup) SwitchCompat switchAutoBackup; @BindView(R.id.containerAutoBackupSettings) View containerSettings; @BindView(R.id.checkBoxAutoBackupDefaultFiles) CheckBox checkBoxDefaultFiles; @BindView(R.id.textViewAutoBackupShowsExportFile) TextView textShowsExportFile; @BindView(R.id.buttonAutoBackupShowsExportFile) Button buttonShowsExportFile; @BindView(R.id.textViewAutoBackupListsExportFile) TextView textListsExportFile; @BindView(R.id.buttonAutoBackupListsExportFile) Button buttonListsExportFile; @BindView(R.id.textViewAutoBackupMoviesExportFile) TextView textMoviesExportFile; @BindView(R.id.buttonAutoBackupMoviesExportFile) Button buttonMoviesExportFile; @BindView(R.id.checkBoxAutoBackupImportWarning) CheckBox checkBoxImportWarning; @BindView(R.id.textViewAutoBackupLastTime) TextView textViewLastAutoBackup; @BindView(R.id.buttonAutoBackupImport) Button buttonImportAutoBackup; @BindView(R.id.progressBarAutoBackup) ProgressBar progressBar; private AsyncTask<Void, Integer, Integer> importTask; private Unbinder unbinder; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* * Try to keep the fragment around on config changes so the backup task * does not have to be finished. */ setRetainInstance(true); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.fragment_auto_backup, container, false); unbinder = ButterKnife.bind(this, v); progressBar.setVisibility(View.GONE); // display last auto-backup date long lastAutoBackupTime = AdvancedSettings.getLastAutoBackupTime(getActivity()); boolean showLastBackupTime = BackupSettings.isUseAutoBackupDefaultFiles(getContext()) ? DataLiberationTools.isAutoBackupDefaultFilesAvailable() : !BackupSettings.isMissingAutoBackupFile(getContext()); textViewLastAutoBackup .setText(getString(R.string.last_auto_backup, showLastBackupTime ? DateUtils.getRelativeDateTimeString(getActivity(), lastAutoBackupTime, DateUtils.SECOND_IN_MILLIS, DateUtils.DAY_IN_MILLIS, 0) : "n/a")); // setup listeners boolean autoBackupEnabled = AdvancedSettings.isAutoBackupEnabled(getContext()); containerSettings.setVisibility(autoBackupEnabled ? View.VISIBLE : View.GONE); switchAutoBackup.setChecked(autoBackupEnabled); switchAutoBackup.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { tryEnableAutoBackup(); } else { setAutoBackupEnabled(false); } } }); checkBoxImportWarning.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { buttonImportAutoBackup.setEnabled(isChecked); } }); buttonImportAutoBackup.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { tryDataLiberationAction(); } }); // selecting custom backup files is only supported on KitKat and up // as we use Storage Access Framework in this case if (AndroidUtils.isKitKatOrHigher()) { checkBoxDefaultFiles.setChecked( BackupSettings.isUseAutoBackupDefaultFiles(getContext())); checkBoxDefaultFiles.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { PreferenceManager.getDefaultSharedPreferences(buttonView.getContext()) .edit() .putBoolean(BackupSettings.KEY_AUTO_BACKUP_USE_DEFAULT_FILES, isChecked) .commit(); updateFileViews(); } }); buttonShowsExportFile.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { DataLiberationTools.selectExportFile(AutoBackupFragment.this, JsonExportTask.EXPORT_JSON_FILE_SHOWS, REQUEST_CODE_SHOWS_EXPORT_URI); } }); buttonListsExportFile.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { DataLiberationTools.selectExportFile(AutoBackupFragment.this, JsonExportTask.EXPORT_JSON_FILE_LISTS, REQUEST_CODE_LISTS_EXPORT_URI); } }); buttonMoviesExportFile.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { DataLiberationTools.selectExportFile(AutoBackupFragment.this, JsonExportTask.EXPORT_JSON_FILE_MOVIES, REQUEST_CODE_MOVIES_EXPORT_URI); } }); } else { checkBoxDefaultFiles.setVisibility(View.GONE); } updateFileViews(); return v; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // restore UI state if (importTask != null && importTask.getStatus() != AsyncTask.Status.FINISHED) { setProgressLock(true); } } @Override public void onDestroyView() { super.onDestroyView(); unbinder.unbind(); } @Override public void onDestroy() { if (importTask != null && importTask.getStatus() != AsyncTask.Status.FINISHED) { importTask.cancel(true); } importTask = null; super.onDestroy(); } @Override public void onProgressUpdate(Integer... values) { if (progressBar == null) { return; } progressBar.setIndeterminate(values[0].equals(values[1])); progressBar.setMax(values[0]); progressBar.setProgress(values[1]); } @Override public void onTaskFinished() { if (!isAdded()) { // don't touch views if fragment is not added to activity any longer return; } if (AndroidUtils.isKitKatOrHigher()) { updateFileViews(); } setProgressLock(false); } private void setProgressLock(boolean isLocked) { if (isLocked) { buttonImportAutoBackup.setEnabled(false); } else { buttonImportAutoBackup.setEnabled(checkBoxImportWarning.isChecked()); } progressBar.setVisibility(isLocked ? View.VISIBLE : View.GONE); checkBoxImportWarning.setEnabled(!isLocked); buttonShowsExportFile.setEnabled(!isLocked); buttonListsExportFile.setEnabled(!isLocked); buttonMoviesExportFile.setEnabled(!isLocked); } private void tryEnableAutoBackup() { // make sure we have the storage write permission if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { // don't have it? request it, do task if granted requestPermissions(new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, REQUEST_CODE_ENABLE_AUTO_BACKUP); return; } setAutoBackupEnabled(true); } private void tryDataLiberationAction() { // make sure we have write permission if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { // don't have it? request it, do task if granted requestPermissions(new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, REQUEST_CODE_IMPORT_AUTOBACKUP); return; } doDataLiberationAction(); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == REQUEST_CODE_ENABLE_AUTO_BACKUP) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { setAutoBackupEnabled(true); } else { if (getView() != null && switchAutoBackup != null) { // disable auto backup as we don't have the required permission switchAutoBackup.setChecked(false); Snackbar.make(getView(), R.string.autobackup_permission_missing, Snackbar.LENGTH_LONG).show(); } } } if (requestCode == REQUEST_CODE_IMPORT_AUTOBACKUP) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { doDataLiberationAction(); } else { if (getView() != null) { Snackbar.make(getView(), R.string.dataliberation_permission_missing, Snackbar.LENGTH_LONG).show(); } } } } private void setAutoBackupEnabled(boolean isEnabled) { PreferenceManager.getDefaultSharedPreferences(getContext()) .edit() .putBoolean(AdvancedSettings.KEY_AUTOBACKUP, isEnabled) .apply(); containerSettings.setVisibility(isEnabled ? View.VISIBLE : View.GONE); } private void doDataLiberationAction() { setProgressLock(true); importTask = new JsonImportTask(getContext(), AutoBackupFragment.this); Utils.executeInOrder(importTask); } @TargetApi(Build.VERSION_CODES.KITKAT) @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode != Activity.RESULT_OK || !isAdded() || data == null) { return; } if (requestCode == REQUEST_CODE_SHOWS_EXPORT_URI || requestCode == REQUEST_CODE_LISTS_EXPORT_URI || requestCode == REQUEST_CODE_MOVIES_EXPORT_URI) { Uri uri = data.getData(); // persist read and write permission for this URI across device reboots try { getContext().getContentResolver() .takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); } catch (SecurityException e) { Timber.e(e, "Could not persist r/w permission for backup file URI."); } if (requestCode == REQUEST_CODE_SHOWS_EXPORT_URI) { BackupSettings.storeFileUri(getContext(), BackupSettings.KEY_AUTO_BACKUP_SHOWS_EXPORT_URI, uri); } else if (requestCode == REQUEST_CODE_LISTS_EXPORT_URI) { BackupSettings.storeFileUri(getContext(), BackupSettings.KEY_AUTO_BACKUP_LISTS_EXPORT_URI, uri); } else { BackupSettings.storeFileUri(getContext(), BackupSettings.KEY_AUTO_BACKUP_MOVIES_EXPORT_URI, uri); } updateFileViews(); } } private void updateFileViews() { if (!BackupSettings.isUseAutoBackupDefaultFiles(getContext()) && AndroidUtils.isKitKatOrHigher()) { setUriOrPlaceholder(textShowsExportFile, BackupSettings.getFileUri(getContext(), BackupSettings.KEY_AUTO_BACKUP_SHOWS_EXPORT_URI)); setUriOrPlaceholder(textListsExportFile, BackupSettings.getFileUri(getContext(), BackupSettings.KEY_AUTO_BACKUP_LISTS_EXPORT_URI)); setUriOrPlaceholder(textMoviesExportFile, BackupSettings.getFileUri(getContext(), BackupSettings.KEY_AUTO_BACKUP_MOVIES_EXPORT_URI)); buttonShowsExportFile.setVisibility(View.VISIBLE); buttonListsExportFile.setVisibility(View.VISIBLE); buttonMoviesExportFile.setVisibility(View.VISIBLE); } else { String path = JsonExportTask.getExportPath(true).toString(); String showsFilePath = path + "/" + JsonExportTask.EXPORT_JSON_FILE_SHOWS; textShowsExportFile.setText(showsFilePath); String listsFilePath = path + "/" + JsonExportTask.EXPORT_JSON_FILE_LISTS; textListsExportFile.setText(listsFilePath); String moviesFilePath = path + "/" + JsonExportTask.EXPORT_JSON_FILE_MOVIES; textMoviesExportFile.setText(moviesFilePath); //noinspection deprecation textShowsExportFile.setTextAppearance(getContext(), PATH_TEXT_APPEARANCE); //noinspection deprecation textListsExportFile.setTextAppearance(getContext(), PATH_TEXT_APPEARANCE); //noinspection deprecation textMoviesExportFile.setTextAppearance(getContext(), PATH_TEXT_APPEARANCE); buttonShowsExportFile.setVisibility(View.GONE); buttonListsExportFile.setVisibility(View.GONE); buttonMoviesExportFile.setVisibility(View.GONE); } } private void setUriOrPlaceholder(TextView textView, Uri uri) { textView.setText(uri == null ? getString(R.string.no_file_selected) : uri.toString()); //noinspection deprecation textView.setTextAppearance(textView.getContext(), uri == null ? R.style.TextAppearance_Body_Highlight_Red : PATH_TEXT_APPEARANCE); } }