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; } }