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