/* Copyright (C) 2016-2020 Alberto, Andreas Shimokawa, Carsten Pfeiffer, Daniele Gobbetti, vanous This file is part of Gadgetbridge. Gadgetbridge is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Gadgetbridge 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.activities; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.database.Cursor; import android.database.sqlite.SQLiteOpenHelper; import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceManager; import android.provider.DocumentsContract; import android.text.TextUtils; import android.util.Pair; import android.view.MenuItem; import android.view.View; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import androidx.core.app.NavUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.database.PeriodicExporter; import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs; import nodomain.freeyourgadget.gadgetbridge.util.ImportExportSharedPreferences; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; public class DataManagementActivity extends AbstractGBActivity { private static final Logger LOG = LoggerFactory.getLogger(DataManagementActivity.class); private static SharedPreferences sharedPrefs; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_data_management); TextView dbPath = findViewById(R.id.activity_data_management_path); dbPath.setText(getExternalPath()); Button exportDBButton = findViewById(R.id.exportDataButton); exportDBButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { exportDB(); } }); Button importDBButton = findViewById(R.id.importDataButton); importDBButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { importDB(); } }); Button showContentDataButton = findViewById(R.id.showContentDataButton); showContentDataButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { File export_path = null; try { export_path = FileUtils.getExternalFilesDir(); } catch (IOException e) { e.printStackTrace(); } AlertDialog.Builder builder = new AlertDialog.Builder(DataManagementActivity.this); builder.setTitle("Export/Import directory content:"); ArrayAdapter directory_listing = new ArrayAdapter(DataManagementActivity.this, android.R.layout.simple_list_item_1, export_path.list()); builder.setSingleChoiceItems(directory_listing, 0, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }); AlertDialog dialog = builder.create(); dialog.show(); } }); int oldDBVisibility = hasOldActivityDatabase() ? View.VISIBLE : View.GONE; TextView deleteOldActivityTitle = findViewById(R.id.mergeOldActivityDataTitle); deleteOldActivityTitle.setVisibility(oldDBVisibility); Button deleteOldActivityDBButton = findViewById(R.id.deleteOldActivityDB); deleteOldActivityDBButton.setVisibility(oldDBVisibility); deleteOldActivityDBButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { deleteOldActivityDbFile(); } }); Button deleteDBButton = findViewById(R.id.emptyDBButton); deleteDBButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { deleteActivityDatabase(); } }); Prefs prefs = GBApplication.getPrefs(); boolean autoExportEnabled = prefs.getBoolean(GBPrefs.AUTO_EXPORT_ENABLED, false); int autoExportInterval = prefs.getInt(GBPrefs.AUTO_EXPORT_INTERVAL, 0); //returns an ugly content://... //String autoExportLocation = prefs.getString(GBPrefs.AUTO_EXPORT_LOCATION, ""); int testExportVisibility = (autoExportInterval > 0 && autoExportEnabled) ? View.VISIBLE : View.GONE; TextView autoExportLocation_label = findViewById(R.id.autoExportLocation_label); autoExportLocation_label.setVisibility(testExportVisibility); TextView autoExportLocation_intro = findViewById(R.id.autoExportLocation_intro); autoExportLocation_intro.setVisibility(testExportVisibility); TextView autoExportLocation_path = findViewById(R.id.autoExportLocation_path); autoExportLocation_path.setVisibility(testExportVisibility); autoExportLocation_path.setText(getAutoExportLocationSummary()); final Context context = getApplicationContext(); Button testExportDBButton = findViewById(R.id.testExportDBButton); testExportDBButton.setVisibility(testExportVisibility); testExportDBButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sendBroadcast(new Intent(context, PeriodicExporter.class)); GB.toast(context, context.getString(R.string.activity_DB_test_export_message), Toast.LENGTH_SHORT, GB.INFO); } }); sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); } private void showDirectoryContent() { } //would rather re-use method of SettingsActivity... but lifecycle... private String getAutoExportLocationSummary() { String autoExportLocation = GBApplication.getPrefs().getString(GBPrefs.AUTO_EXPORT_LOCATION, null); if (autoExportLocation == null) { return ""; } Uri uri = Uri.parse(autoExportLocation); try { return AndroidUtils.getFilePath(getApplicationContext(), uri); } catch (IllegalArgumentException e) { try { Cursor cursor = getContentResolver().query( uri, new String[]{DocumentsContract.Document.COLUMN_DISPLAY_NAME}, null, null, null, null ); if (cursor != null && cursor.moveToFirst()) { return cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME)); } } catch (Exception fdfsdfds) { LOG.error("Error", fdfsdfds); } } return ""; } private boolean hasOldActivityDatabase() { return new DBHelper(this).existsDB("ActivityDatabase"); } private String getExternalPath() { try { return FileUtils.getExternalFilesDir().getAbsolutePath(); } catch (Exception ex) { LOG.warn("Unable to get external files dir", ex); } return getString(R.string.dbmanagementactivvity_cannot_access_export_path); } private void exportShared() { try { File myPath = FileUtils.getExternalFilesDir(); File myFile = new File(myPath, "Export_preference"); ImportExportSharedPreferences.exportToFile(sharedPrefs, myFile, null); } catch (IOException ex) { GB.toast(this, getString(R.string.dbmanagementactivity_error_exporting_shared, ex.getMessage()), Toast.LENGTH_LONG, GB.ERROR, ex); } try (DBHandler lockHandler = GBApplication.acquireDB()) { List activeDevices = DBHelper.getActiveDevices(lockHandler.getDaoSession()); for (Device dbDevice : activeDevices) { SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier()); if (sharedPrefs != null) { File myPath = FileUtils.getExternalFilesDir(); File myFile = new File(myPath, "Export_preference_" + dbDevice.getIdentifier()); try { ImportExportSharedPreferences.exportToFile(deviceSharedPrefs, myFile, null); } catch (Exception ignore) { // some devices no not have device specific preferences } } } } catch (Exception e) { GB.toast("Error exporting device specific preferences", Toast.LENGTH_SHORT, GB.ERROR, e); } } private void importShared() { try { File myPath = FileUtils.getExternalFilesDir(); File myFile = new File(myPath, "Export_preference"); ImportExportSharedPreferences.importFromFile(sharedPrefs, myFile); } catch (Exception ex) { GB.toast(DataManagementActivity.this, getString(R.string.dbmanagementactivity_error_importing_db, ex.getMessage()), Toast.LENGTH_LONG, GB.ERROR, ex); } try (DBHandler lockHandler = GBApplication.acquireDB()) { List activeDevices = DBHelper.getActiveDevices(lockHandler.getDaoSession()); for (Device dbDevice : activeDevices) { SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier()); if (sharedPrefs != null) { File myPath = FileUtils.getExternalFilesDir(); File myFile = new File(myPath, "Export_preference_" + dbDevice.getIdentifier()); try { ImportExportSharedPreferences.importFromFile(deviceSharedPrefs, myFile); } catch (Exception ignore) { // some devices no not have device specific preferences } } } } catch (Exception e) { GB.toast("Error importing device specific preferences", Toast.LENGTH_SHORT, GB.ERROR); } } private void exportDB() { new AlertDialog.Builder(this) .setCancelable(true) .setTitle(R.string.dbmanagementactivity_export_data_title) .setMessage(R.string.dbmanagementactivity_export_confirmation) .setPositiveButton(R.string.activity_DB_ExportButton, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { try (DBHandler dbHandler = GBApplication.acquireDB()) { exportShared(); DBHelper helper = new DBHelper(DataManagementActivity.this); File dir = FileUtils.getExternalFilesDir(); File destFile = helper.exportDB(dbHandler, dir); GB.toast(DataManagementActivity.this, getString(R.string.dbmanagementactivity_exported_to, destFile.getAbsolutePath()), Toast.LENGTH_LONG, GB.INFO); } catch (Exception ex) { GB.toast(DataManagementActivity.this, getString(R.string.dbmanagementactivity_error_exporting_db, ex.getMessage()), Toast.LENGTH_LONG, GB.ERROR, ex); } } }) .setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }) .show(); } private void importDB() { new AlertDialog.Builder(this) .setCancelable(true) .setTitle(R.string.dbmanagementactivity_import_data_title) .setMessage(R.string.dbmanagementactivity_overwrite_database_confirmation) .setPositiveButton(R.string.dbmanagementactivity_overwrite, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { try (DBHandler dbHandler = GBApplication.acquireDB()) { DBHelper helper = new DBHelper(DataManagementActivity.this); File dir = FileUtils.getExternalFilesDir(); SQLiteOpenHelper sqLiteOpenHelper = dbHandler.getHelper(); File sourceFile = new File(dir, sqLiteOpenHelper.getDatabaseName()); helper.importDB(dbHandler, sourceFile); helper.validateDB(sqLiteOpenHelper); GB.toast(DataManagementActivity.this, getString(R.string.dbmanagementactivity_import_successful), Toast.LENGTH_LONG, GB.INFO); } catch (Exception ex) { GB.toast(DataManagementActivity.this, getString(R.string.dbmanagementactivity_error_importing_db, ex.getMessage()), Toast.LENGTH_LONG, GB.ERROR, ex); } importShared(); } }) .setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }) .show(); } private void deleteActivityDatabase() { new AlertDialog.Builder(this) .setCancelable(true) .setTitle(R.string.dbmanagementactivity_delete_activity_data_title) .setMessage(R.string.dbmanagementactivity_really_delete_entire_db) .setPositiveButton(R.string.Delete, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (GBApplication.deleteActivityDatabase(DataManagementActivity.this)) { GB.toast(DataManagementActivity.this, getString(R.string.dbmanagementactivity_database_successfully_deleted), Toast.LENGTH_SHORT, GB.INFO); } else { GB.toast(DataManagementActivity.this, getString(R.string.dbmanagementactivity_db_deletion_failed), Toast.LENGTH_SHORT, GB.INFO); } } }) .setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }) .show(); } private void deleteOldActivityDbFile() { new AlertDialog.Builder(this).setCancelable(true); new AlertDialog.Builder(this).setTitle(R.string.dbmanagementactivity_delete_old_activity_db); new AlertDialog.Builder(this).setMessage(R.string.dbmanagementactivity_delete_old_activitydb_confirmation); new AlertDialog.Builder(this).setPositiveButton(R.string.Delete, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (GBApplication.deleteOldActivityDatabase(DataManagementActivity.this)) { GB.toast(DataManagementActivity.this, getString(R.string.dbmanagementactivity_old_activity_db_successfully_deleted), Toast.LENGTH_SHORT, GB.INFO); } else { GB.toast(DataManagementActivity.this, getString(R.string.dbmanagementactivity_old_activity_db_deletion_failed), Toast.LENGTH_SHORT, GB.INFO); } } }); new AlertDialog.Builder(this).setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }); new AlertDialog.Builder(this).show(); } @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == android.R.id.home) { NavUtils.navigateUpFromSameTask(this); return true; } return super.onOptionsItemSelected(item); } }