April 21, 2021
Implementing backup and restore feature of SQLite Database to comply with Scoped Storage Android 11
package com.abc.def;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
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.nio.channels.FileChannel;
import java.text.SimpleDateFormat;
import java.util.Date;
import static android.os.Build.VERSION.SDK_INT;
public class BackupRestoreActivity extends AppCompatActivity {
Button btnBackup, btnRestore;
SQLiteHelper sqLiteHelper;
File mediaStorageDir;
public static final int REQUEST_WRITE_STORAGE = 1;
TextView txtStatus;
/// ---
private static final int CREATE_BACKUP_REQUEST_CODE = 1000;
private static final int PICK_RESTORE_FILE_REQUEST_CODE = 2000;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_backup_restore);
sqLiteHelper = new SQLiteHelper(BackupRestoreActivity.this);
btnBackup = (Button)findViewById(R.id.btnBackup);
btnRestore = (Button)findViewById(R.id.btnRestore);
txtStatus = (TextView)findViewById(R.id.txtStatus);
btnBackup.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// Inform picker that we would like to create a file
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
// Filter to only show results that can be "opened", such as a file
intent.addCategory(Intent.CATEGORY_OPENABLE);
// Set the mimetype of the file we're creating
intent.setType("application/octet-stream");
// Set a suggested title for the backup file with custom .dtt extension
// (though the user can change it within the picker)
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm");
final String backupFileName = sdf.format(new Date()) + ".bak";
intent.putExtra(Intent.EXTRA_TITLE, backupFileName);
// Open the picker
startActivityForResult(intent, CREATE_BACKUP_REQUEST_CODE);
}
});
btnRestore.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// Inform picker that we would like to open a file
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
String[] mimeTypes = {"application/octet-stream"};
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
startActivityForResult(intent, PICK_RESTORE_FILE_REQUEST_CODE);
}
});
}
private static final int READ_REQUEST_CODE = 42;
@Override
public void onActivityResult(int requestCode, int resultCode,
Intent resultData) {
super.onActivityResult(requestCode,resultCode,resultData);
// The ACTION_OPEN_DOCUMENT intent was sent with the request code
// READ_REQUEST_CODE. If the request code seen here doesn't match, it's the
// response to some other intent, and the code below shouldn't run at all.
if (resultCode != Activity.RESULT_OK) {
return;
}
// to create backup
if (requestCode == CREATE_BACKUP_REQUEST_CODE) {
// get URI of file created by picker
Uri backupFileUri = resultData.getData();
// Perform the actual backup procedure
// backupDatabaseOnSeparateThread(backupFileUri);
newexportDB(backupFileUri);
String msg = "Backup has been saved!";
Toast.makeText(this, msg , Toast.LENGTH_SHORT).show();
txtStatus.setText(msg);
Log.i("FILE SELECT", "Save backup in: " + backupFileUri.getPath().toString());
}
// to create backup ends
// // to restore
if (requestCode == PICK_RESTORE_FILE_REQUEST_CODE) {
try {
// Get URI representing the file chosen in the picker.
Uri uri = resultData.getData();
InputStream inputStream = getContentResolver().openInputStream(uri);
// Perform the actual restore procedure
newrestoreDB(inputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
//showErrorSnackbarOnUiThread(e);
//showLogError(LOG_TAG, e, "");
}
}
}
private void newrestoreDB(InputStream _inputStream){
boolean exceptionOccurred = false;
final long startTime = System.currentTimeMillis();
try {
String tempDir = getCacheDir().getPath().toString();
File newfile = new File(tempDir + "/" + "newbackup.db");
copyInputStreamToFile(_inputStream, newfile);
restoreDatabaseFile(BackupRestoreActivity.this, newfile, sqLiteHelper.getDatabaseName());
sqLiteHelper.getWritableDatabase().close();
}catch (Exception e)
{
}
}
// Restore database file stored at tempDir/nameOfFileToRestore.
// This method is inside my DBHelper class.
public void restoreDatabaseFile(Context context, File newDbFile, String nameOfFileToRestore)
throws IOException {
// Close the SQLiteOpenHelper so it will commit the created empty database
// to internal storage.
sqLiteHelper.close();
File currentDbFile = new File(context.getDatabasePath(nameOfFileToRestore).getPath());
//File newDbFile = new File(tempDir + "/" + nameOfFileToRestore);
if (newDbFile.exists()) {
copyFile(newDbFile, currentDbFile, true);
String msg = "Database has been restored!";
Toast.makeText(this, msg , Toast.LENGTH_SHORT).show();
txtStatus.setText(msg);
}
}
// Copy an InputStream to a File.
//
private void copyInputStreamToFile(InputStream in, File file) {
OutputStream out = null;
try {
out = new FileOutputStream(file);
byte[] buf = new byte[1024];
int len;
while((len=in.read(buf))>0){
out.write(buf,0,len);
}
}
catch (Exception e) {
e.printStackTrace();
}
finally {
// Ensure that the InputStreams are closed even if there's an exception.
try {
if ( out != null ) {
out.close();
}
// If you want to close the "in" InputStream yourself then remove this
// from here but ensure that you close it yourself eventually.
in.close();
}
catch ( IOException e ) {
e.printStackTrace();
}
}
}
private void newexportDB(Uri _backupfileuri){
try {
File tempDirectory = getCacheDir();
FileChannel source = null;
FileChannel destination = null;
String databaseBackupFile = getApplicationContext().getDatabasePath(sqLiteHelper.getDatabaseName()).toString();// getFilesDir().getPath() + "databases/SQLiteDatabase.db" ; // "/data/data/com.zakasoft.cashreceipt/databases/SQLiteDatabase.db" ; // this.getDatabasePath(sqLiteHelper.getDatabaseName()).getPath();
File dbbackupfile = backUpDatabaseFile(databaseBackupFile, tempDirectory.getPath() + "/" + sqLiteHelper.getDatabaseName());
Uri zipFileUri = Uri.fromFile(dbbackupfile);
InputStream inputStream = getContentResolver().openInputStream(zipFileUri);
OutputStream outputStream = getContentResolver().openOutputStream(_backupfileuri);
// Instead of Files, we pass in streams derived from the URIs.
// This is because we don't have File write access to the final directory.
copyFile(inputStream,outputStream);
} catch (final Exception e)
{
}
}
// Create a copy of the file at pathOfFileToBackUp and save it to destinationFilePath.
// This method is inside my DBHelper class.
public File backUpDatabaseFile(String pathOfFileToBackUp, String destinationFilePath)
throws IOException {
File currentDbFile = new File(pathOfFileToBackUp);
File newDb = new File(destinationFilePath);
if (currentDbFile.exists()) {
copyFile(currentDbFile, newDb, false);
return newDb;
}
return null;
}
public static boolean copyFile(InputStream input, OutputStream output) {
try {
byte[] buf = new byte[1024];
int len;
while ((len = input.read(buf)) > 0) {
output.write(buf, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
return false;
} finally {
try {
if (input != null)
input.close();
if (output != null)
output.close();
} catch (Exception e) {
}
}
return true;
}
// Copy fromFile to toFile, using traditional file IO
public static boolean copyFile(File fromFile, File toFile, boolean bDeleteOriginalFile)
throws IOException {
boolean bSuccess = true;
FileInputStream inputStream = new FileInputStream(fromFile);
FileOutputStream outputStream = new FileOutputStream(toFile);
FileChannel fromChannel = null;
FileChannel toChannel = null;
try {
fromChannel = inputStream.getChannel();
toChannel = outputStream.getChannel();
fromChannel.transferTo(0, fromChannel.size(), toChannel);
} catch (Exception e) {
bSuccess = false;
} finally {
try {
if (fromChannel != null) {
fromChannel.close();
}
} finally {
if (toChannel != null) {
toChannel.close();
}
}
if (bDeleteOriginalFile) {
fromFile.delete();
}
}
return bSuccess;
}
}

