1package eu.siacs.conversations.ui;
2
3import android.content.ComponentName;
4import android.content.Context;
5import android.content.DialogInterface;
6import android.content.Intent;
7import android.content.ServiceConnection;
8import android.net.Uri;
9import android.os.Bundle;
10import android.os.IBinder;
11import android.util.Log;
12import android.view.LayoutInflater;
13import android.view.Menu;
14import android.view.MenuItem;
15import android.view.View;
16
17import androidx.appcompat.app.AlertDialog;
18import androidx.core.content.ContextCompat;
19import androidx.databinding.DataBindingUtil;
20
21import com.google.android.material.snackbar.Snackbar;
22
23import java.io.IOException;
24import java.util.List;
25
26import eu.siacs.conversations.Config;
27import eu.siacs.conversations.R;
28import eu.siacs.conversations.databinding.ActivityImportBackupBinding;
29import eu.siacs.conversations.databinding.DialogEnterPasswordBinding;
30import eu.siacs.conversations.services.ImportBackupService;
31import eu.siacs.conversations.ui.adapter.BackupFileAdapter;
32
33public class ImportBackupActivity extends ActionBarActivity implements ServiceConnection, ImportBackupService.OnBackupFilesLoaded, BackupFileAdapter.OnItemClickedListener, ImportBackupService.OnBackupProcessed {
34
35 private ActivityImportBackupBinding binding;
36
37 private BackupFileAdapter backupFileAdapter;
38 private ImportBackupService service;
39
40 private boolean mLoadingState = false;
41
42 private int mTheme;
43
44 @Override
45 protected void onCreate(final Bundle savedInstanceState) {
46 super.onCreate(savedInstanceState);
47 binding = DataBindingUtil.setContentView(this, R.layout.activity_import_backup);
48 setSupportActionBar(binding.toolbar);
49 setLoadingState(savedInstanceState != null && savedInstanceState.getBoolean("loading_state", false));
50 this.backupFileAdapter = new BackupFileAdapter();
51 this.binding.list.setAdapter(this.backupFileAdapter);
52 this.backupFileAdapter.setOnItemClickedListener(this);
53 }
54
55 @Override
56 public boolean onCreateOptionsMenu(final Menu menu) {
57 getMenuInflater().inflate(R.menu.import_backup, menu);
58 final MenuItem openBackup = menu.findItem(R.id.action_open_backup_file);
59 openBackup.setVisible(!this.mLoadingState);
60 return true;
61 }
62
63 @Override
64 public void onSaveInstanceState(Bundle bundle) {
65 bundle.putBoolean("loading_state", this.mLoadingState);
66 super.onSaveInstanceState(bundle);
67 }
68
69 @Override
70 public void onStart() {
71 super.onStart();
72 bindService(new Intent(this, ImportBackupService.class), this, Context.BIND_AUTO_CREATE);
73 final Intent intent = getIntent();
74 if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction()) && !this.mLoadingState) {
75 Uri uri = intent.getData();
76 if (uri != null) {
77 openBackupFileFromUri(uri, true);
78 }
79 }
80 }
81
82 @Override
83 public void onStop() {
84 super.onStop();
85 if (this.service != null) {
86 this.service.removeOnBackupProcessedListener(this);
87 }
88 unbindService(this);
89 }
90
91 @Override
92 public void onServiceConnected(ComponentName name, IBinder service) {
93 ImportBackupService.ImportBackupServiceBinder binder = (ImportBackupService.ImportBackupServiceBinder) service;
94 this.service = binder.getService();
95 this.service.addOnBackupProcessedListener(this);
96 setLoadingState(this.service.getLoadingState());
97 this.service.loadBackupFiles(this);
98 }
99
100 @Override
101 public void onServiceDisconnected(ComponentName name) {
102 this.service = null;
103 }
104
105 @Override
106 public void onBackupFilesLoaded(final List<ImportBackupService.BackupFile> files) {
107 runOnUiThread(() -> backupFileAdapter.setFiles(files));
108 }
109
110 @Override
111 public void onClick(final ImportBackupService.BackupFile backupFile) {
112 showEnterPasswordDialog(backupFile, false);
113 }
114
115 private void openBackupFileFromUri(final Uri uri, final boolean finishOnCancel) {
116 new Thread(() -> {
117 try {
118 final ImportBackupService.BackupFile backupFile = ImportBackupService.BackupFile.read(this, uri);
119 runOnUiThread(() -> showEnterPasswordDialog(backupFile, finishOnCancel));
120 } catch (final IOException | IllegalArgumentException e) {
121 Log.d(Config.LOGTAG, "unable to open backup file " + uri, e);
122 runOnUiThread(() -> Snackbar.make(binding.coordinator, R.string.not_a_backup_file, Snackbar.LENGTH_LONG).show());
123 }
124 }).start();
125 }
126
127 private void showEnterPasswordDialog(final ImportBackupService.BackupFile backupFile, final boolean finishOnCancel) {
128 final DialogEnterPasswordBinding enterPasswordBinding = DataBindingUtil.inflate(LayoutInflater.from(this), R.layout.dialog_enter_password, null, false);
129 Log.d(Config.LOGTAG, "attempting to import " + backupFile.getUri());
130 enterPasswordBinding.explain.setText(getString(R.string.enter_password_to_restore, backupFile.getHeader().getJid().toString()));
131 AlertDialog.Builder builder = new AlertDialog.Builder(this);
132 builder.setView(enterPasswordBinding.getRoot());
133 builder.setTitle(R.string.enter_password);
134 builder.setNegativeButton(R.string.cancel, (dialog, which) -> {
135 if (finishOnCancel) {
136 finish();
137 }
138 });
139 builder.setPositiveButton(R.string.restore, null);
140 builder.setCancelable(false);
141 final AlertDialog dialog = builder.create();
142 dialog.setOnShowListener((d) -> {
143 dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> {
144 final String password = enterPasswordBinding.accountPassword.getEditableText().toString();
145 if (password.isEmpty()) {
146 enterPasswordBinding.accountPasswordLayout.setError(getString(R.string.please_enter_password));
147 return;
148 }
149 final Uri uri = backupFile.getUri();
150 Intent intent = new Intent(this, ImportBackupService.class);
151 intent.setAction(Intent.ACTION_SEND);
152 intent.putExtra("password", password);
153 if ("file".equals(uri.getScheme())) {
154 intent.putExtra("file", uri.getPath());
155 } else {
156 intent.setData(uri);
157 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
158 }
159 setLoadingState(true);
160 ContextCompat.startForegroundService(this, intent);
161 d.dismiss();
162 });
163 });
164 dialog.show();
165 }
166
167 private void setLoadingState(final boolean loadingState) {
168 binding.coordinator.setVisibility(loadingState ? View.GONE : View.VISIBLE);
169 binding.inProgress.setVisibility(loadingState ? View.VISIBLE : View.GONE);
170 setTitle(loadingState ? R.string.restoring_backup : R.string.restore_backup);
171 configureActionBar(getSupportActionBar(), !loadingState);
172 this.mLoadingState = loadingState;
173 invalidateOptionsMenu();
174 }
175
176 @Override
177 public void onActivityResult(int requestCode, int resultCode, Intent intent) {
178 super.onActivityResult(requestCode, resultCode, intent);
179 if (resultCode == RESULT_OK) {
180 if (requestCode == 0xbac) {
181 openBackupFileFromUri(intent.getData(), false);
182 }
183 }
184 }
185
186 @Override
187 public void onAccountAlreadySetup() {
188 runOnUiThread(() -> {
189 setLoadingState(false);
190 Snackbar.make(binding.coordinator, R.string.account_already_setup, Snackbar.LENGTH_LONG).show();
191 });
192 }
193
194 @Override
195 public void onBackupRestored() {
196 runOnUiThread(() -> {
197 Intent intent = new Intent(this, ConversationActivity.class);
198 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
199 startActivity(intent);
200 finish();
201 });
202 }
203
204 @Override
205 public void onBackupDecryptionFailed() {
206 runOnUiThread(() -> {
207 setLoadingState(false);
208 Snackbar.make(binding.coordinator, R.string.unable_to_decrypt_backup, Snackbar.LENGTH_LONG).show();
209 });
210 }
211
212 @Override
213 public void onBackupRestoreFailed() {
214 runOnUiThread(() -> {
215 setLoadingState(false);
216 Snackbar.make(binding.coordinator, R.string.unable_to_restore_backup, Snackbar.LENGTH_LONG).show();
217 });
218 }
219
220 @Override
221 public boolean onOptionsItemSelected(MenuItem item) {
222 if (item.getItemId() == R.id.action_open_backup_file) {
223 openBackupFile();
224 return true;
225 }
226 return super.onOptionsItemSelected(item);
227 }
228
229 private void openBackupFile() {
230 final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
231 intent.setType("*/*");
232 intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false);
233 intent.addCategory(Intent.CATEGORY_OPENABLE);
234 startActivityForResult(Intent.createChooser(intent, getString(R.string.open_backup)), 0xbac);
235 }
236}