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