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