1package eu.siacs.conversations.ui;
2
3import android.content.ActivityNotFoundException;
4import android.content.Intent;
5import android.os.Bundle;
6import android.security.KeyChain;
7import android.security.KeyChainAliasCallback;
8import android.support.v7.app.ActionBar;
9import android.support.v7.app.AlertDialog;
10import android.util.Pair;
11import android.view.ContextMenu;
12import android.view.ContextMenu.ContextMenuInfo;
13import android.view.Menu;
14import android.view.MenuItem;
15import android.view.View;
16import android.widget.AdapterView;
17import android.widget.AdapterView.AdapterContextMenuInfo;
18import android.widget.AdapterView.OnItemClickListener;
19import android.widget.ListView;
20import android.widget.Toast;
21
22import org.openintents.openpgp.util.OpenPgpApi;
23
24import java.util.ArrayList;
25import java.util.List;
26import java.util.concurrent.atomic.AtomicBoolean;
27
28import eu.siacs.conversations.Config;
29import eu.siacs.conversations.R;
30import eu.siacs.conversations.entities.Account;
31import eu.siacs.conversations.services.XmppConnectionService;
32import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
33import eu.siacs.conversations.ui.adapter.AccountAdapter;
34import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
35import eu.siacs.conversations.xmpp.XmppConnection;
36import rocks.xmpp.addr.Jid;
37
38public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate, KeyChainAliasCallback, XmppConnectionService.OnAccountCreated {
39
40 private final String STATE_SELECTED_ACCOUNT = "selected_account";
41
42 protected Account selectedAccount = null;
43 protected Jid selectedAccountJid = null;
44
45 protected final List<Account> accountList = new ArrayList<>();
46 protected ListView accountListView;
47 protected AccountAdapter mAccountAdapter;
48 protected AtomicBoolean mInvokedAddAccount = new AtomicBoolean(false);
49
50 protected Pair<Integer, Intent> mPostponedActivityResult = null;
51
52 @Override
53 public void onAccountUpdate() {
54 refreshUi();
55 }
56
57 @Override
58 protected void refreshUiReal() {
59 synchronized (this.accountList) {
60 accountList.clear();
61 accountList.addAll(xmppConnectionService.getAccounts());
62 }
63 ActionBar actionBar = getSupportActionBar();
64 if (actionBar != null) {
65 actionBar.setHomeButtonEnabled(this.accountList.size() > 0);
66 actionBar.setDisplayHomeAsUpEnabled(this.accountList.size() > 0);
67 }
68 invalidateOptionsMenu();
69 mAccountAdapter.notifyDataSetChanged();
70 }
71
72 @Override
73 protected void onCreate(Bundle savedInstanceState) {
74
75 super.onCreate(savedInstanceState);
76
77 setContentView(R.layout.activity_manage_accounts);
78 setSupportActionBar(findViewById(R.id.toolbar));
79 configureActionBar(getSupportActionBar());
80 if (savedInstanceState != null) {
81 String jid = savedInstanceState.getString(STATE_SELECTED_ACCOUNT);
82 if (jid != null) {
83 try {
84 this.selectedAccountJid = Jid.of(jid);
85 } catch (IllegalArgumentException e) {
86 this.selectedAccountJid = null;
87 }
88 }
89 }
90
91 accountListView = (ListView) findViewById(R.id.account_list);
92 this.mAccountAdapter = new AccountAdapter(this, accountList);
93 accountListView.setAdapter(this.mAccountAdapter);
94 accountListView.setOnItemClickListener(new OnItemClickListener() {
95
96 @Override
97 public void onItemClick(AdapterView<?> arg0, View view,
98 int position, long arg3) {
99 switchToAccount(accountList.get(position));
100 }
101 });
102 registerForContextMenu(accountListView);
103 }
104
105 @Override
106 protected void onStart() {
107 super.onStart();
108 final int theme = findTheme();
109 if (this.mTheme != theme) {
110 recreate();
111 }
112 }
113
114 @Override
115 public void onSaveInstanceState(final Bundle savedInstanceState) {
116 if (selectedAccount != null) {
117 savedInstanceState.putString(STATE_SELECTED_ACCOUNT, selectedAccount.getJid().asBareJid().toString());
118 }
119 super.onSaveInstanceState(savedInstanceState);
120 }
121
122 @Override
123 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
124 super.onCreateContextMenu(menu, v, menuInfo);
125 ManageAccountActivity.this.getMenuInflater().inflate(
126 R.menu.manageaccounts_context, menu);
127 AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
128 this.selectedAccount = accountList.get(acmi.position);
129 if (this.selectedAccount.isEnabled()) {
130 menu.findItem(R.id.mgmt_account_enable).setVisible(false);
131 menu.findItem(R.id.mgmt_account_announce_pgp).setVisible(Config.supportOpenPgp());
132 } else {
133 menu.findItem(R.id.mgmt_account_disable).setVisible(false);
134 menu.findItem(R.id.mgmt_account_announce_pgp).setVisible(false);
135 menu.findItem(R.id.mgmt_account_publish_avatar).setVisible(false);
136 }
137 menu.setHeaderTitle(this.selectedAccount.getJid().asBareJid().toString());
138 }
139
140 @Override
141 void onBackendConnected() {
142 if (selectedAccountJid != null) {
143 this.selectedAccount = xmppConnectionService.findAccountByJid(selectedAccountJid);
144 }
145 refreshUiReal();
146 if (this.mPostponedActivityResult != null) {
147 this.onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
148 }
149 if (Config.X509_VERIFICATION && this.accountList.size() == 0) {
150 if (mInvokedAddAccount.compareAndSet(false, true)) {
151 addAccountFromKey();
152 }
153 }
154 }
155
156 @Override
157 public boolean onCreateOptionsMenu(Menu menu) {
158 getMenuInflater().inflate(R.menu.manageaccounts, menu);
159 MenuItem enableAll = menu.findItem(R.id.action_enable_all);
160 MenuItem addAccount = menu.findItem(R.id.action_add_account);
161 MenuItem addAccountWithCertificate = menu.findItem(R.id.action_add_account_with_cert);
162
163 if (Config.X509_VERIFICATION) {
164 addAccount.setVisible(false);
165 addAccountWithCertificate.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
166 }
167
168 if (!accountsLeftToEnable()) {
169 enableAll.setVisible(false);
170 }
171 MenuItem disableAll = menu.findItem(R.id.action_disable_all);
172 if (!accountsLeftToDisable()) {
173 disableAll.setVisible(false);
174 }
175 return true;
176 }
177
178 @Override
179 public boolean onContextItemSelected(MenuItem item) {
180 switch (item.getItemId()) {
181 case R.id.mgmt_account_publish_avatar:
182 publishAvatar(selectedAccount);
183 return true;
184 case R.id.mgmt_account_disable:
185 disableAccount(selectedAccount);
186 return true;
187 case R.id.mgmt_account_enable:
188 enableAccount(selectedAccount);
189 return true;
190 case R.id.mgmt_account_delete:
191 deleteAccount(selectedAccount);
192 return true;
193 case R.id.mgmt_account_announce_pgp:
194 publishOpenPGPPublicKey(selectedAccount);
195 return true;
196 default:
197 return super.onContextItemSelected(item);
198 }
199 }
200
201 @Override
202 public boolean onOptionsItemSelected(MenuItem item) {
203 if (MenuDoubleTabUtil.shouldIgnoreTap()) {
204 return false;
205 }
206 switch (item.getItemId()) {
207 case R.id.action_add_account:
208 startActivity(new Intent(getApplicationContext(),
209 EditAccountActivity.class));
210 break;
211 case R.id.action_disable_all:
212 disableAllAccounts();
213 break;
214 case R.id.action_enable_all:
215 enableAllAccounts();
216 break;
217 case R.id.action_add_account_with_cert:
218 addAccountFromKey();
219 break;
220 default:
221 break;
222 }
223 return super.onOptionsItemSelected(item);
224 }
225
226 @Override
227 public boolean onNavigateUp() {
228 if (xmppConnectionService.getConversations().size() == 0) {
229 Intent contactsIntent = new Intent(this,
230 StartConversationActivity.class);
231 contactsIntent.setFlags(
232 // if activity exists in stack, pop the stack and go back to it
233 Intent.FLAG_ACTIVITY_CLEAR_TOP |
234 // otherwise, make a new task for it
235 Intent.FLAG_ACTIVITY_NEW_TASK |
236 // don't use the new activity animation; finish
237 // animation runs instead
238 Intent.FLAG_ACTIVITY_NO_ANIMATION);
239 startActivity(contactsIntent);
240 finish();
241 return true;
242 } else {
243 return super.onNavigateUp();
244 }
245 }
246
247 public void onClickTglAccountState(Account account, boolean enable) {
248 if (enable) {
249 enableAccount(account);
250 } else {
251 disableAccount(account);
252 }
253 }
254
255 private void addAccountFromKey() {
256 try {
257 KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null);
258 } catch (ActivityNotFoundException e) {
259 Toast.makeText(this, R.string.device_does_not_support_certificates, Toast.LENGTH_LONG).show();
260 }
261 }
262
263 private void publishAvatar(Account account) {
264 Intent intent = new Intent(getApplicationContext(),
265 PublishProfilePictureActivity.class);
266 intent.putExtra(EXTRA_ACCOUNT, account.getJid().toString());
267 startActivity(intent);
268 }
269
270 private void disableAllAccounts() {
271 List<Account> list = new ArrayList<>();
272 synchronized (this.accountList) {
273 for (Account account : this.accountList) {
274 if (account.isEnabled()) {
275 list.add(account);
276 }
277 }
278 }
279 for (Account account : list) {
280 disableAccount(account);
281 }
282 }
283
284 private boolean accountsLeftToDisable() {
285 synchronized (this.accountList) {
286 for (Account account : this.accountList) {
287 if (account.isEnabled()) {
288 return true;
289 }
290 }
291 return false;
292 }
293 }
294
295 private boolean accountsLeftToEnable() {
296 synchronized (this.accountList) {
297 for (Account account : this.accountList) {
298 if (!account.isEnabled()) {
299 return true;
300 }
301 }
302 return false;
303 }
304 }
305
306 private void enableAllAccounts() {
307 List<Account> list = new ArrayList<>();
308 synchronized (this.accountList) {
309 for (Account account : this.accountList) {
310 if (!account.isEnabled()) {
311 list.add(account);
312 }
313 }
314 }
315 for (Account account : list) {
316 enableAccount(account);
317 }
318 }
319
320 private void disableAccount(Account account) {
321 account.setOption(Account.OPTION_DISABLED, true);
322 if (!xmppConnectionService.updateAccount(account)) {
323 Toast.makeText(this,R.string.unable_to_update_account,Toast.LENGTH_SHORT).show();
324 }
325 }
326
327 private void enableAccount(Account account) {
328 account.setOption(Account.OPTION_DISABLED, false);
329 final XmppConnection connection = account.getXmppConnection();
330 if (connection != null) {
331 connection.resetEverything();
332 }
333 if (!xmppConnectionService.updateAccount(account)) {
334 Toast.makeText(this,R.string.unable_to_update_account,Toast.LENGTH_SHORT).show();
335 }
336 }
337
338 private void publishOpenPGPPublicKey(Account account) {
339 if (ManageAccountActivity.this.hasPgp()) {
340 announcePgp(selectedAccount, null,null, onOpenPGPKeyPublished);
341 } else {
342 this.showInstallPgpDialog();
343 }
344 }
345
346 private void deleteAccount(final Account account) {
347 AlertDialog.Builder builder = new AlertDialog.Builder(this);
348 builder.setTitle(getString(R.string.mgmt_account_are_you_sure));
349 builder.setIconAttribute(android.R.attr.alertDialogIcon);
350 builder.setMessage(getString(R.string.mgmt_account_delete_confirm_text));
351 builder.setPositiveButton(getString(R.string.delete),
352 (dialog, which) -> {
353 xmppConnectionService.deleteAccount(account);
354 selectedAccount = null;
355 if (xmppConnectionService.getAccounts().size() == 0 && Config.MAGIC_CREATE_DOMAIN != null) {
356 WelcomeActivity.launch(this);
357 }
358 });
359 builder.setNegativeButton(getString(R.string.cancel), null);
360 builder.create().show();
361 }
362
363 @Override
364 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
365 super.onActivityResult(requestCode, resultCode, data);
366 if (resultCode == RESULT_OK) {
367 if (xmppConnectionServiceBound) {
368 if (requestCode == REQUEST_CHOOSE_PGP_ID) {
369 if (data.getExtras().containsKey(OpenPgpApi.EXTRA_SIGN_KEY_ID)) {
370 selectedAccount.setPgpSignId(data.getExtras().getLong(OpenPgpApi.EXTRA_SIGN_KEY_ID));
371 announcePgp(selectedAccount, null, null, onOpenPGPKeyPublished);
372 } else {
373 choosePgpSignId(selectedAccount);
374 }
375 } else if (requestCode == REQUEST_ANNOUNCE_PGP) {
376 announcePgp(selectedAccount, null, data, onOpenPGPKeyPublished);
377 }
378 this.mPostponedActivityResult = null;
379 } else {
380 this.mPostponedActivityResult = new Pair<>(requestCode, data);
381 }
382 }
383 }
384
385 @Override
386 public void alias(String alias) {
387 if (alias != null) {
388 xmppConnectionService.createAccountFromKey(alias, this);
389 }
390 }
391
392 @Override
393 public void onAccountCreated(Account account) {
394 Intent intent = new Intent(this, EditAccountActivity.class);
395 intent.putExtra("jid", account.getJid().asBareJid().toString());
396 intent.putExtra("init", true);
397 startActivity(intent);
398 }
399
400 @Override
401 public void informUser(final int r) {
402 runOnUiThread(() -> Toast.makeText(ManageAccountActivity.this, r, Toast.LENGTH_LONG).show());
403 }
404}