/*
* Copyright (c) 2012 Ngewi Fet <ngewif@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gnucash.android.ui.settings;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.preference.CheckBoxPreference;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceFragmentCompat;
import android.support.v7.preference.PreferenceManager;
import android.util.Log;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import com.dropbox.core.android.Auth;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.drive.Drive;
import com.google.android.gms.drive.DriveFolder;
import com.google.android.gms.drive.MetadataChangeSet;
import org.gnucash.android.R;
import org.gnucash.android.app.GnuCashApplication;
import org.gnucash.android.db.adapter.BooksDbAdapter;
import org.gnucash.android.export.Exporter;
import org.gnucash.android.export.xml.GncXmlExporter;
import org.gnucash.android.importer.ImportAsyncTask;
import org.gnucash.android.ui.settings.dialog.OwnCloudDialogFragment;
import org.gnucash.android.util.BookUtils;
import java.io.File;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
/**
* Fragment for displaying general preferences
* @author Ngewi Fet <ngewif@gmail.com>
*
*/
public class BackupPreferenceFragment extends PreferenceFragmentCompat implements
Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener {
/**
* Collects references to the UI elements and binds click listeners
*/
private static final int REQUEST_LINK_TO_DBX = 0x11;
public static final int REQUEST_RESOLVE_CONNECTION = 0x12;
/**
* Request code for the backup file where to save backups
*/
private static final int REQUEST_BACKUP_FILE = 0x13;
/**
* Testing app key for DropBox API
*/
final static public String DROPBOX_APP_KEY = "dhjh8ke9wf05948";
/**
* Testing app secret for DropBox API
*/
final static public String DROPBOX_APP_SECRET = "h2t9fphj3nr4wkw";
/**
* String for tagging log statements
*/
public static final String LOG_TAG = "BackupPrefFragment";
/**
* Client for Google Drive Sync
*/
public static GoogleApiClient mGoogleApiClient;
@Override
public void onCreatePreferences(Bundle bundle, String s) {
addPreferencesFromResource(R.xml.fragment_backup_preferences);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActionBar actionBar = ((AppCompatActivity)getActivity()).getSupportActionBar();
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(R.string.title_backup_prefs);
mGoogleApiClient = getGoogleApiClient(getActivity());
}
@Override
public void onResume() {
super.onResume();
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
//if we are returning from DropBox authentication, save the key which was generated
String keyDefaultEmail = getString(R.string.key_default_export_email);
Preference pref = findPreference(keyDefaultEmail);
String defaultEmail = sharedPrefs.getString(keyDefaultEmail, null);
if (defaultEmail != null && !defaultEmail.trim().isEmpty()){
pref.setSummary(defaultEmail);
}
pref.setOnPreferenceChangeListener(this);
String keyDefaultExportFormat = getString(R.string.key_default_export_format);
pref = findPreference(keyDefaultExportFormat);
String defaultExportFormat = sharedPrefs.getString(keyDefaultExportFormat, null);
if (defaultExportFormat != null && !defaultExportFormat.trim().isEmpty()){
pref.setSummary(defaultExportFormat);
}
pref.setOnPreferenceChangeListener(this);
pref = findPreference(getString(R.string.key_restore_backup));
pref.setOnPreferenceClickListener(this);
pref = findPreference(getString(R.string.key_create_backup));
pref.setOnPreferenceClickListener(this);
pref = findPreference(getString(R.string.key_backup_location));
pref.setOnPreferenceClickListener(this);
String defaultBackupLocation = BookUtils.getBookBackupFileUri(BooksDbAdapter.getInstance().getActiveBookUID());
if (defaultBackupLocation != null){
pref.setSummary(Uri.parse(defaultBackupLocation).getAuthority());
}
pref = findPreference(getString(R.string.key_dropbox_sync));
pref.setOnPreferenceClickListener(this);
toggleDropboxPreference(pref);
pref = findPreference(getString(R.string.key_owncloud_sync));
pref.setOnPreferenceClickListener(this);
toggleOwnCloudPreference(pref);
}
@Override
public boolean onPreferenceClick(Preference preference) {
String key = preference.getKey();
if (key.equals(getString(R.string.key_restore_backup))){
restoreBackup();
}
if (key.equals(getString(R.string.key_backup_location))){
Intent createIntent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
createIntent.setType("application/zip");
createIntent.addCategory(Intent.CATEGORY_OPENABLE);
String bookName = BooksDbAdapter.getInstance().getActiveBookDisplayName();
createIntent.putExtra(Intent.EXTRA_TITLE, Exporter.sanitizeFilename(bookName)+ "_" + getString(R.string.label_backup_filename));
startActivityForResult(createIntent, REQUEST_BACKUP_FILE);
}
if (key.equals(getString(R.string.key_dropbox_sync))){
toggleDropboxSync();
toggleDropboxPreference(preference);
}
if (key.equals(getString(R.string.key_owncloud_sync))){
toggleOwnCloudSync(preference);
toggleOwnCloudPreference(preference);
}
if (key.equals(getString(R.string.key_create_backup))){
boolean result = GncXmlExporter.createBackup();
int msg = result ? R.string.toast_backup_successful : R.string.toast_backup_failed;
Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
}
return false;
}
/**
* Listens for changes to the preference and sets the preference summary to the new value
* @param preference Preference which has been changed
* @param newValue New value for the changed preference
* @return <code>true</code> if handled, <code>false</code> otherwise
*/
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
preference.setSummary(newValue.toString());
if (preference.getKey().equals(getString(R.string.key_default_currency))){
GnuCashApplication.setDefaultCurrencyCode(newValue.toString());
}
if (preference.getKey().equals(getString(R.string.key_default_export_email))){
String emailSetting = newValue.toString();
if (emailSetting == null || emailSetting.trim().isEmpty()){
preference.setSummary(R.string.summary_default_export_email);
}
}
if (preference.getKey().equals(getString(R.string.key_default_export_format))){
String exportFormat = newValue.toString();
if (exportFormat == null || exportFormat.trim().isEmpty()){
preference.setSummary(R.string.summary_default_export_format);
}
}
return true;
}
/**
* Toggles the checkbox of the DropBox Sync preference if a DropBox account is linked
* @param pref DropBox Sync preference
*/
public void toggleDropboxPreference(Preference pref) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
String accessToken = prefs.getString(getString(R.string.key_dropbox_access_token), null);
((CheckBoxPreference)pref).setChecked(accessToken != null);
}
/**
* Toggles the checkbox of the ownCloud Sync preference if an ownCloud account is linked
* @param pref ownCloud Sync preference
*/
public void toggleOwnCloudPreference(Preference pref) {
SharedPreferences mPrefs = getActivity().getSharedPreferences(getString(R.string.owncloud_pref), Context.MODE_PRIVATE);
((CheckBoxPreference)pref).setChecked(mPrefs.getBoolean(getString(R.string.owncloud_sync), false));
}
/**
* Toggles the checkbox of the GoogleDrive Sync preference if a Google Drive account is linked
* @param pref Google Drive Sync preference
*/
public void toggleGoogleDrivePreference(Preference pref){
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
String appFolderId = sharedPreferences.getString(getString(R.string.key_google_drive_app_folder_id),null);
((CheckBoxPreference)pref).setChecked(appFolderId != null);
}
/**
* Toggles the authorization state of a DropBox account.
* If a link exists, it is removed else DropBox authorization is started
*/
private void toggleDropboxSync() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
String accessToken = prefs.getString(getString(R.string.key_dropbox_access_token), null);
if (accessToken == null){
Auth.startOAuth2Authentication(getActivity(), getString(R.string.dropbox_app_key));
} else {
prefs.edit().remove(getString(R.string.key_dropbox_access_token)).apply();
}
}
/**
* Toggles synchronization with Google Drive on or off
*/
private void toggleGoogleDriveSync(){
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
final String appFolderId = sharedPreferences.getString(getString(R.string.key_google_drive_app_folder_id), null);
if (appFolderId != null){
sharedPreferences.edit().remove(getString(R.string.key_google_drive_app_folder_id)).commit(); //commit (not apply) because we need it to be saved *now*
mGoogleApiClient.disconnect();
} else {
mGoogleApiClient.connect();
}
}
/**
* Toggles synchronization with ownCloud on or off
*/
private void toggleOwnCloudSync(Preference pref){
SharedPreferences mPrefs = getActivity().getSharedPreferences(getString(R.string.owncloud_pref), Context.MODE_PRIVATE);
if (mPrefs.getBoolean(getString(R.string.owncloud_sync), false))
mPrefs.edit().putBoolean(getString(R.string.owncloud_sync), false).apply();
else {
OwnCloudDialogFragment ocDialog = OwnCloudDialogFragment.newInstance(pref);
ocDialog.show(getActivity().getSupportFragmentManager(), "owncloud_dialog");
}
}
public static GoogleApiClient getGoogleApiClient(final Context context) {
return new GoogleApiClient.Builder(context)
.addApi(Drive.API)
.addScope(Drive.SCOPE_APPFOLDER)
.addScope(Drive.SCOPE_FILE)
.addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
@Override
public void onConnected(Bundle bundle) {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
String appFolderId = sharedPreferences.getString(context.getString(R.string.key_google_drive_app_folder_id), null);
if (appFolderId == null) {
MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
.setTitle(context.getString(R.string.app_name)).build();
Drive.DriveApi.getRootFolder(mGoogleApiClient).createFolder(
mGoogleApiClient, changeSet).setResultCallback(new ResultCallback<DriveFolder.DriveFolderResult>() {
@Override
public void onResult(DriveFolder.DriveFolderResult result) {
if (!result.getStatus().isSuccess()) {
Log.e(LOG_TAG, "Error creating the application folder");
return;
}
String folderId = result.getDriveFolder().getDriveId().toString();
PreferenceManager.getDefaultSharedPreferences(context)
.edit().putString(context.getString(R.string.key_google_drive_app_folder_id),
folderId).commit(); //commit because we need it to be saved *now*
}
});
}
Toast.makeText(context, R.string.toast_connected_to_google_drive, Toast.LENGTH_SHORT).show();
}
@Override
public void onConnectionSuspended(int i) {
Toast.makeText(context, "Connection to Google Drive suspended!", Toast.LENGTH_LONG).show();
}
})
.addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Log.e(PreferenceActivity.class.getName(), "Connection to Google Drive failed");
if (connectionResult.hasResolution() && context instanceof Activity) {
try {
Log.e(BackupPreferenceFragment.class.getName(), "Trying resolution of Google API connection failure");
connectionResult.startResolutionForResult((Activity) context, REQUEST_RESOLVE_CONNECTION);
} catch (IntentSender.SendIntentException e) {
Log.e(BackupPreferenceFragment.class.getName(), e.getMessage());
Toast.makeText(context, R.string.toast_unable_to_connect_to_google_drive, Toast.LENGTH_LONG).show();
}
} else {
if (context instanceof Activity)
GooglePlayServicesUtil.getErrorDialog(connectionResult.getErrorCode(), (Activity) context, 0).show();
}
}
})
.build();
}
/**
* Opens a dialog for a user to select a backup to restore and then restores the backup
*/
private void restoreBackup() {
Log.i("Settings", "Opening GnuCash XML backups for restore");
String bookUID = BooksDbAdapter.getInstance().getActiveBookUID();
final String defaultBackupFile = BookUtils.getBookBackupFileUri(bookUID);
if (defaultBackupFile != null){
android.support.v7.app.AlertDialog.Builder builder = new android.support.v7.app.AlertDialog.Builder(getActivity())
.setTitle(R.string.title_confirm_restore_backup)
.setMessage(R.string.msg_confirm_restore_backup_into_new_book)
.setNegativeButton(R.string.btn_cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.setPositiveButton(R.string.btn_restore, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
new ImportAsyncTask(getActivity()).execute(Uri.parse(defaultBackupFile));
}
});
builder.create().show();
return; //stop here if the default backup file exists
}
//If no default location was set, look in the internal SD card location
File[] backupFiles = new File(Exporter.getBackupFolderPath(bookUID)).listFiles();
if (backupFiles == null || backupFiles.length == 0){
android.support.v7.app.AlertDialog.Builder builder = new android.support.v7.app.AlertDialog.Builder(getActivity())
.setTitle(R.string.title_no_backups_found)
.setMessage(R.string.msg_no_backups_to_restore_from)
.setNegativeButton(R.string.label_dismiss, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.create().show();
return;
}
Arrays.sort(backupFiles);
List<File> backupFilesList = Arrays.asList(backupFiles);
Collections.reverse(backupFilesList);
final File[] sortedBackupFiles = (File[]) backupFilesList.toArray();
final ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(getActivity(), android.R.layout.select_dialog_singlechoice);
final DateFormat dateFormatter = SimpleDateFormat.getDateTimeInstance();
for (File backupFile : sortedBackupFiles) {
long time = Exporter.getExportTime(backupFile.getName());
if (time > 0)
arrayAdapter.add(dateFormatter.format(new Date(time)));
else //if no timestamp was found in the filename, just use the name
arrayAdapter.add(backupFile.getName());
}
AlertDialog.Builder restoreDialogBuilder = new AlertDialog.Builder(getActivity());
restoreDialogBuilder.setTitle(R.string.title_select_backup_to_restore);
restoreDialogBuilder.setNegativeButton(R.string.alert_dialog_cancel,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
restoreDialogBuilder.setAdapter(arrayAdapter, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
File backupFile = sortedBackupFiles[which];
new ImportAsyncTask(getActivity()).execute(Uri.fromFile(backupFile));
}
});
restoreDialogBuilder.create().show();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode){
case REQUEST_LINK_TO_DBX:
Preference preference = findPreference(getString(R.string.key_dropbox_sync));
if (preference == null) //if we are in a preference header fragment, this may return null
break;
toggleDropboxPreference(preference);
break;
case REQUEST_RESOLVE_CONNECTION:
if (resultCode == Activity.RESULT_OK) {
mGoogleApiClient.connect();
Preference pref = findPreference(getString(R.string.key_dropbox_sync));
if (pref == null) //if we are in a preference header fragment, this may return null
break;
toggleDropboxPreference(pref);
}
break;
case REQUEST_BACKUP_FILE:
if (resultCode == Activity.RESULT_OK){
Uri backupFileUri = null;
if (data != null){
backupFileUri = data.getData();
}
final int takeFlags = data.getFlags()
& (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
getActivity().getContentResolver().takePersistableUriPermission(backupFileUri, takeFlags);
PreferenceActivity.getActiveBookSharedPreferences()
.edit()
.putString(BookUtils.KEY_BACKUP_FILE, backupFileUri.toString())
.apply();
Preference pref = findPreference(getString(R.string.key_backup_location));
pref.setSummary(backupFileUri.getAuthority());
}
break;
}
}
}