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