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