/*
* Copyright (C) 2016 Glucosio Foundation
*
* This file is part of Glucosio.
*
* Glucosio is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* Glucosio is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Glucosio. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
package org.glucosio.android.activity;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.github.paolorotolo.expandableheightlistview.ExpandableHeightListView;
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.DriveApi;
import com.google.android.gms.drive.DriveContents;
import com.google.android.gms.drive.DriveFile;
import com.google.android.gms.drive.DriveFolder;
import com.google.android.gms.drive.DriveId;
import com.google.android.gms.drive.DriveResource;
import com.google.android.gms.drive.Metadata;
import com.google.android.gms.drive.MetadataBuffer;
import com.google.android.gms.drive.MetadataChangeSet;
import com.google.android.gms.drive.OpenFileActivityBuilder;
import com.google.android.gms.drive.query.Filters;
import com.google.android.gms.drive.query.Query;
import com.google.android.gms.drive.query.SearchableField;
import com.google.android.gms.drive.query.SortOrder;
import com.google.android.gms.drive.query.SortableField;
import com.google.firebase.crash.FirebaseCrash;
import org.glucosio.android.GlucosioApplication;
import org.glucosio.android.R;
import org.glucosio.android.adapter.BackupAdapter;
import org.glucosio.android.backup.Backup;
import org.glucosio.android.object.GlucosioBackup;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import io.realm.Realm;
import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper;
public class BackupActivity extends AppCompatActivity {
private static final int REQUEST_CODE_PICKER = 2;
private static final int REQUEST_CODE_PICKER_FOLDER = 4;
private static final String TAG = "glucosio_drive_backup";
private static final String BACKUP_FOLDER_KEY = "backup_folder";
private Backup backup;
private GoogleApiClient mGoogleApiClient;
private TextView folderTextView;
private IntentSender intentPicker;
private Realm realm;
private String backupFolder;
private ExpandableHeightListView backupListView;
private SharedPreferences sharedPref;
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.backup_drive_activity);
GlucosioApplication glucosioApplication = (GlucosioApplication) getApplicationContext();
sharedPref = getPreferences(Context.MODE_PRIVATE);
realm = glucosioApplication.getDBHandler().getRealmInstance();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle(getResources().getString(R.string.title_activity_backup_drive));
backup = glucosioApplication.getBackup();
backup.init(this);
connectClient();
mGoogleApiClient = backup.getClient();
Button backupButton = (Button) findViewById(R.id.activity_backup_drive_button_backup);
TextView manageButton = (TextView) findViewById(R.id.activity_backup_drive_button_manage_drive);
folderTextView = (TextView) findViewById(R.id.activity_backup_drive_textview_folder);
LinearLayout selectFolderButton = (LinearLayout) findViewById(R.id.activity_backup_drive_button_folder);
backupListView = (ExpandableHeightListView) findViewById(R.id.activity_backup_drive_listview_restore);
backupListView.setExpanded(true);
backupButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Open Folder picker, then upload the file on Drive
openFolderPicker(true);
}
});
selectFolderButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Check first if a folder is already selected
if (!"".equals(backupFolder)) {
//Start the picker to choose a folder
//False because we don't want to upload the backup on drive then
openFolderPicker(false);
}
}
});
manageButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
openOnDrive(DriveId.decodeFromString(backupFolder));
}
});
// Show backup folder, if exists
backupFolder = sharedPref.getString(BACKUP_FOLDER_KEY, "");
if (!("").equals(backupFolder)) {
setBackupFolderTitle(DriveId.decodeFromString(backupFolder));
manageButton.setVisibility(View.VISIBLE);
}
// Populate backup list
if (!("").equals(backupFolder)) {
getBackupsFromDrive(DriveId.decodeFromString(backupFolder).asDriveFolder());
}
}
private void setBackupFolderTitle(DriveId id) {
id.asDriveFolder().getMetadata((mGoogleApiClient)).setResultCallback(
new ResultCallback<DriveResource.MetadataResult>() {
@Override
public void onResult(@NonNull DriveResource.MetadataResult result) {
if (!result.getStatus().isSuccess()) {
showErrorDialog();
return;
}
Metadata metadata = result.getMetadata();
folderTextView.setText(metadata.getTitle());
}
}
);
}
private void openFolderPicker(boolean uploadToDrive) {
if (uploadToDrive) {
// First we check if a backup folder is set
if (TextUtils.isEmpty(backupFolder)) {
try {
if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
if (intentPicker == null)
intentPicker = buildIntent();
//Start the picker to choose a folder
startIntentSenderForResult(
intentPicker, REQUEST_CODE_PICKER, null, 0, 0, 0);
}
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "Unable to send intent", e);
showErrorDialog();
}
} else {
uploadToDrive(DriveId.decodeFromString(backupFolder));
}
} else {
try {
intentPicker = null;
if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
if (intentPicker == null)
intentPicker = buildIntent();
//Start the picker to choose a folder
startIntentSenderForResult(
intentPicker, REQUEST_CODE_PICKER_FOLDER, null, 0, 0, 0);
}
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "Unable to send intent", e);
showErrorDialog();
}
}
}
private IntentSender buildIntent() {
return Drive.DriveApi
.newOpenFileActivityBuilder()
.setMimeType(new String[]{DriveFolder.MIME_TYPE})
.build(mGoogleApiClient);
}
private void getBackupsFromDrive(DriveFolder folder) {
final Activity activity = this;
SortOrder sortOrder = new SortOrder.Builder()
.addSortDescending(SortableField.MODIFIED_DATE).build();
Query query = new Query.Builder()
.addFilter(Filters.eq(SearchableField.TITLE, "glucosio.realm"))
.addFilter(Filters.eq(SearchableField.TRASHED, false))
.setSortOrder(sortOrder)
.build();
folder.queryChildren(mGoogleApiClient, query)
.setResultCallback(new ResultCallback<DriveApi.MetadataBufferResult>() {
private ArrayList<GlucosioBackup> backupsArray = new ArrayList<>();
@Override
public void onResult(@NonNull DriveApi.MetadataBufferResult result) {
MetadataBuffer buffer = result.getMetadataBuffer();
int size = buffer.getCount();
for (int i = 0; i < size; i++) {
Metadata metadata = buffer.get(i);
DriveId driveId = metadata.getDriveId();
Date modifiedDate = metadata.getModifiedDate();
long backupSize = metadata.getFileSize();
backupsArray.add(new GlucosioBackup(driveId, modifiedDate, backupSize));
}
backupListView.setAdapter(new BackupAdapter(activity, R.layout.activity_backup_drive_restore_item, backupsArray));
}
});
}
public void downloadFromDrive(DriveFile file) {
file.open(mGoogleApiClient, DriveFile.MODE_READ_ONLY, null)
.setResultCallback(new ResultCallback<DriveApi.DriveContentsResult>() {
@Override
public void onResult(@NonNull DriveApi.DriveContentsResult result) {
if (!result.getStatus().isSuccess()) {
showErrorDialog();
return;
}
// DriveContents object contains pointers
// to the actual byte stream
DriveContents contents = result.getDriveContents();
InputStream input = contents.getInputStream();
try {
File file = new File(realm.getPath());
OutputStream output = new FileOutputStream(file);
try {
try {
byte[] buffer = new byte[4 * 1024]; // or other buffer size
int read;
while ((read = input.read(buffer)) != -1) {
output.write(buffer, 0, read);
}
output.flush();
} finally {
safeCloseClosable(input);
}
} catch (Exception e) {
reportToFirebase(e, "Error downloading backup from drive");
e.printStackTrace();
}
} catch (FileNotFoundException e) {
reportToFirebase(e, "Error downloading backup from drive, file not found");
e.printStackTrace();
} finally {
safeCloseClosable(input);
}
Toast.makeText(getApplicationContext(), R.string.activity_backup_drive_message_restart, Toast.LENGTH_LONG).show();
// Reboot app
Intent mStartActivity = new Intent(getApplicationContext(), MainActivity.class);
int mPendingIntentId = 123456;
PendingIntent mPendingIntent = PendingIntent.getActivity(getApplicationContext(), mPendingIntentId, mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager mgr = (AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
System.exit(0);
}
});
}
private void safeCloseClosable(Closeable closeable) {
try {
closeable.close();
} catch (IOException e) {
reportToFirebase(e, "Error downloading backup from drive, IO Exception");
e.printStackTrace();
}
}
private void uploadToDrive(DriveId mFolderDriveId) {
if (mFolderDriveId != null) {
//Create the file on GDrive
final DriveFolder folder = mFolderDriveId.asDriveFolder();
Drive.DriveApi.newDriveContents(mGoogleApiClient)
.setResultCallback(new ResultCallback<DriveApi.DriveContentsResult>() {
@Override
public void onResult(@NonNull DriveApi.DriveContentsResult result) {
if (!result.getStatus().isSuccess()) {
Log.e(TAG, "Error while trying to create new file contents");
showErrorDialog();
return;
}
final DriveContents driveContents = result.getDriveContents();
// Perform I/O off the UI thread.
new Thread() {
@Override
public void run() {
// write content to DriveContents
OutputStream outputStream = driveContents.getOutputStream();
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream(new File(realm.getPath()));
} catch (FileNotFoundException e) {
reportToFirebase(e, "Error uploading backup from drive, file not found");
showErrorDialog();
e.printStackTrace();
}
byte[] buf = new byte[1024];
int bytesRead;
try {
if (inputStream != null) {
while ((bytesRead = inputStream.read(buf)) > 0) {
outputStream.write(buf, 0, bytesRead);
}
}
} catch (IOException e) {
showErrorDialog();
e.printStackTrace();
}
MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
.setTitle("glucosio.realm")
.setMimeType("text/plain")
.build();
// create a file in selected folder
folder.createFile(mGoogleApiClient, changeSet, driveContents)
.setResultCallback(new ResultCallback<DriveFolder.DriveFileResult>() {
@Override
public void onResult(@NonNull DriveFolder.DriveFileResult result) {
if (!result.getStatus().isSuccess()) {
Log.d(TAG, "Error while trying to create the file");
showErrorDialog();
finish();
return;
}
showSuccessDialog();
finish();
}
});
}
}.start();
}
});
}
}
private void openOnDrive(DriveId driveId) {
driveId.asDriveFolder().getMetadata((mGoogleApiClient)).setResultCallback(
new ResultCallback<DriveResource.MetadataResult>() {
@Override
public void onResult(@NonNull DriveResource.MetadataResult result) {
if (!result.getStatus().isSuccess()) {
showErrorDialog();
return;
}
Metadata metadata = result.getMetadata();
String url = metadata.getAlternateLink();
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
startActivity(i);
}
}
);
}
@Override
protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
switch (requestCode) {
case 1:
if (resultCode == RESULT_OK) {
backup.start();
}
break;
// REQUEST_CODE_PICKER
case 2:
intentPicker = null;
if (resultCode == RESULT_OK) {
//Get the folder drive id
DriveId mFolderDriveId = data.getParcelableExtra(
OpenFileActivityBuilder.EXTRA_RESPONSE_DRIVE_ID);
saveBackupFolder(mFolderDriveId.encodeToString());
uploadToDrive(mFolderDriveId);
}
break;
// REQUEST_CODE_SELECT
case 3:
if (resultCode == RESULT_OK) {
// get the selected item's ID
DriveId driveId = data.getParcelableExtra(
OpenFileActivityBuilder.EXTRA_RESPONSE_DRIVE_ID);
DriveFile file = driveId.asDriveFile();
downloadFromDrive(file);
} else {
showErrorDialog();
}
finish();
break;
// REQUEST_CODE_PICKER_FOLDER
case 4:
if (resultCode == RESULT_OK) {
//Get the folder drive id
DriveId mFolderDriveId = data.getParcelableExtra(
OpenFileActivityBuilder.EXTRA_RESPONSE_DRIVE_ID);
saveBackupFolder(mFolderDriveId.encodeToString());
// Restart activity to apply changes
Intent intent = getIntent();
finish();
startActivity(intent);
}
break;
}
}
private void saveBackupFolder(String folderPath) {
SharedPreferences.Editor editor = sharedPref.edit();
editor.putString(BACKUP_FOLDER_KEY, folderPath);
editor.apply();
}
private void showSuccessDialog() {
Toast.makeText(getApplicationContext(), R.string.activity_backup_drive_success, Toast.LENGTH_SHORT).show();
}
private void showErrorDialog() {
Toast.makeText(getApplicationContext(), R.string.activity_backup_drive_failed, Toast.LENGTH_SHORT).show();
}
private void reportToFirebase(Exception e, String message) {
FirebaseCrash.log(message);
FirebaseCrash.report(e);
}
public void connectClient() {
backup.start();
}
public void disconnectClient() {
backup.stop();
}
public boolean onOptionsItemSelected(MenuItem item) {
finish();
return true;
}
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase));
}
}