ManageAccountActivity.java

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