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