/* Copyright (C) 2024 José Rebelo 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.net.Uri; import android.os.Bundle; import android.os.Handler; import android.view.MenuItem; import android.view.View; import android.widget.ProgressBar; import android.widget.TextView; import androidx.activity.OnBackPressedCallback; import androidx.annotation.Nullable; import androidx.documentfile.provider.DocumentFile; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.util.backup.AbstractZipBackupJob; import nodomain.freeyourgadget.gadgetbridge.util.backup.ZipBackupCallback; import nodomain.freeyourgadget.gadgetbridge.util.backup.ZipBackupExportJob; import nodomain.freeyourgadget.gadgetbridge.util.backup.ZipBackupImportJob; public class BackupRestoreProgressActivity extends AbstractGBActivity { private static final Logger LOG = LoggerFactory.getLogger(BackupRestoreProgressActivity.class); public static final String EXTRA_URI = "uri"; public static final String EXTRA_ACTION = "action"; // import/export private boolean jobFinished = false; private Uri uri; private String action; private Thread mThread; private AbstractZipBackupJob mZipBackupJob; @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_backup_restore_progress); final Bundle extras = getIntent().getExtras(); if (extras == null) { LOG.error("No extras"); finish(); return; } uri = extras.getParcelable(EXTRA_URI); if (uri == null) { LOG.error("No uri"); finish(); return; } action = extras.getString(EXTRA_ACTION); if (action == null) { LOG.error("No action"); finish(); return; } final TextView backupRestoreHint = findViewById(R.id.backupRestoreHint); final ProgressBar backupRestoreProgressBar = findViewById(R.id.backupRestoreProgressBar); final TextView backupRestoreProgressText = findViewById(R.id.backupRestoreProgressText); final TextView backupRestoreProgressPercentage = findViewById(R.id.backupRestoreProgressPercentage); final ZipBackupCallback zipBackupCallback = new ZipBackupCallback() { @Override public void onProgress(final int progress, final String message) { backupRestoreProgressBar.setIndeterminate(progress == 0); backupRestoreProgressBar.setProgress(progress); backupRestoreProgressText.setText(message); backupRestoreProgressPercentage.setText(getString(R.string.battery_percentage_str, String.valueOf(progress))); } @Override public void onSuccess(final String warnings) { jobFinished = true; backupRestoreHint.setVisibility(View.GONE); backupRestoreProgressBar.setProgress(100); backupRestoreProgressPercentage.setText(getString(R.string.battery_percentage_str, "100")); switch (action) { case "import": backupRestoreProgressText.setText(R.string.backup_restore_import_complete); final StringBuilder message = new StringBuilder(); message.append(getString(R.string.backup_restore_restart_summary, getString(R.string.app_name))); if (warnings != null) { message.append("\n\n").append(warnings); } new MaterialAlertDialogBuilder(BackupRestoreProgressActivity.this) .setCancelable(false) .setIcon(R.drawable.ic_sync) .setTitle(R.string.backup_restore_restart_title) .setMessage(message.toString()) .setOnCancelListener((dialog -> { finish(); GBApplication.restart(); })) .setPositiveButton(R.string.ok, (dialog, which) -> { finish(); GBApplication.restart(); }).show(); break; case "export": backupRestoreProgressText.setText(R.string.backup_restore_export_complete); break; } } @Override public void onFailure(@Nullable final String errorMessage) { jobFinished = true; switch (action) { case "import": backupRestoreHint.setText(R.string.backup_restore_error_import); break; case "export": backupRestoreHint.setText(R.string.backup_restore_error_export); break; } backupRestoreProgressText.setText(errorMessage); backupRestoreProgressPercentage.setVisibility(View.GONE); if ("export".equals(action)) { final DocumentFile documentFile = DocumentFile.fromSingleUri(BackupRestoreProgressActivity.this, uri); if (documentFile != null) { documentFile.delete(); } } } }; switch (action) { case "import": backupRestoreHint.setText(getString(R.string.backup_restore_do_not_exit, getString(R.string.backup_restore_importing))); mZipBackupJob = new ZipBackupImportJob(GBApplication.getContext(), zipBackupCallback, uri); break; case "export": backupRestoreHint.setText(getString(R.string.backup_restore_do_not_exit, getString(R.string.backup_restore_exporting))); mZipBackupJob = new ZipBackupExportJob(GBApplication.getContext(), zipBackupCallback, uri); break; default: LOG.error("Unknown action {}", action); finish(); return; } mThread = new Thread(mZipBackupJob, "gb-backup-restore"); mThread.start(); getOnBackPressedDispatcher().addCallback(new OnBackPressedCallback(true) { @Override public void handleOnBackPressed() { confirmExit(); } }); } @Override public boolean onOptionsItemSelected(final MenuItem item) { final int itemId = item.getItemId(); if (itemId == android.R.id.home) { // back button confirmExit(); return true; } return super.onOptionsItemSelected(item); } private void confirmExit() { if (jobFinished) { finish(); return; } final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this) .setCancelable(true) .setIcon(R.drawable.ic_warning) .setTitle(R.string.backup_restore_abort_title) .setPositiveButton(R.string.backup_restore_abort_title, (dialog, which) -> { if (mZipBackupJob != null) { LOG.info("Aborting {}", action); final Handler handler = new Handler(getMainLooper()); mZipBackupJob.abort(); new Thread(() -> { try { mThread.join(60_000); } catch (final InterruptedException ignored) { } handler.post(() -> { LOG.info("Aborted {}", action); if ("export".equals(action)) { // Delete the incomplete export file final DocumentFile documentFile = DocumentFile.fromSingleUri(BackupRestoreProgressActivity.this, uri); if (documentFile != null) { documentFile.delete(); } } finish(); }); }).start(); } }) .setNegativeButton(R.string.Cancel, (dialog, which) -> { }); if ("import".equals(action)) { builder.setMessage(R.string.backup_restore_abort_import_confirmation); } else { builder.setMessage(R.string.backup_restore_abort_export_confirmation); } builder.show(); } }