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;
 18import android.widget.AdapterView.AdapterContextMenuInfo;
 19import android.widget.AdapterView.OnItemClickListener;
 20import android.widget.ListView;
 21import android.widget.Toast;
 22
 23import org.openintents.openpgp.util.OpenPgpApi;
 24
 25import java.util.ArrayList;
 26import java.util.List;
 27import java.util.concurrent.atomic.AtomicBoolean;
 28
 29import eu.siacs.conversations.Config;
 30import eu.siacs.conversations.R;
 31import eu.siacs.conversations.entities.Account;
 32import eu.siacs.conversations.services.XmppConnectionService;
 33import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
 34import eu.siacs.conversations.ui.adapter.AccountAdapter;
 35import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
 36import eu.siacs.conversations.xmpp.XmppConnection;
 37import rocks.xmpp.addr.Jid;
 38
 39import static eu.siacs.conversations.utils.PermissionUtils.allGranted;
 40import static eu.siacs.conversations.utils.PermissionUtils.writeGranted;
 41
 42public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate, KeyChainAliasCallback, XmppConnectionService.OnAccountCreated, AccountAdapter.OnTglAccountState {
 43
 44    private final String STATE_SELECTED_ACCOUNT = "selected_account";
 45
 46    private static final int REQUEST_IMPORT_BACKUP = 0x63fb;
 47
 48    protected Account selectedAccount = null;
 49    protected Jid selectedAccountJid = null;
 50
 51    protected final List<Account> accountList = new ArrayList<>();
 52    protected ListView accountListView;
 53    protected AccountAdapter mAccountAdapter;
 54    protected AtomicBoolean mInvokedAddAccount = new AtomicBoolean(false);
 55
 56    protected Pair<Integer, Intent> mPostponedActivityResult = null;
 57
 58    @Override
 59    public void onAccountUpdate() {
 60        refreshUi();
 61    }
 62
 63    @Override
 64    protected void refreshUiReal() {
 65        synchronized (this.accountList) {
 66            accountList.clear();
 67            accountList.addAll(xmppConnectionService.getAccounts());
 68        }
 69        ActionBar actionBar = getSupportActionBar();
 70        if (actionBar != null) {
 71            actionBar.setHomeButtonEnabled(this.accountList.size() > 0);
 72            actionBar.setDisplayHomeAsUpEnabled(this.accountList.size() > 0);
 73        }
 74        invalidateOptionsMenu();
 75        mAccountAdapter.notifyDataSetChanged();
 76    }
 77
 78    @Override
 79    protected void onCreate(Bundle savedInstanceState) {
 80
 81        super.onCreate(savedInstanceState);
 82
 83        setContentView(R.layout.activity_manage_accounts);
 84        setSupportActionBar(findViewById(R.id.toolbar));
 85        configureActionBar(getSupportActionBar());
 86        if (savedInstanceState != null) {
 87            String jid = savedInstanceState.getString(STATE_SELECTED_ACCOUNT);
 88            if (jid != null) {
 89                try {
 90                    this.selectedAccountJid = Jid.of(jid);
 91                } catch (IllegalArgumentException e) {
 92                    this.selectedAccountJid = null;
 93                }
 94            }
 95        }
 96
 97        accountListView = findViewById(R.id.account_list);
 98        this.mAccountAdapter = new AccountAdapter(this, accountList);
 99        accountListView.setAdapter(this.mAccountAdapter);
100        accountListView.setOnItemClickListener((arg0, view, position, arg3) -> switchToAccount(accountList.get(position)));
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        if (MenuDoubleTabUtil.shouldIgnoreTap()) {
203            return false;
204        }
205        switch (item.getItemId()) {
206            case R.id.action_add_account:
207                startActivity(new Intent(this, EditAccountActivity.class));
208                break;
209            case R.id.action_import_backup:
210                if (hasStoragePermission(REQUEST_IMPORT_BACKUP)) {
211                    startActivity(new Intent(this, ImportBackupActivity.class));
212                }
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
230    @Override
231    public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
232        if (grantResults.length > 0) {
233            if (allGranted(grantResults)) {
234                switch (requestCode) {
235                    case REQUEST_IMPORT_BACKUP:
236                        startActivity(new Intent(this, ImportBackupActivity.class));
237                        break;
238                }
239            } else {
240                Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show();
241            }
242        }
243        if (writeGranted(grantResults, permissions)) {
244            if (xmppConnectionService != null) {
245                xmppConnectionService.restartFileObserver();
246            }
247        }
248    }
249
250    @Override
251    public boolean onNavigateUp() {
252        if (xmppConnectionService.getConversations().size() == 0) {
253            Intent contactsIntent = new Intent(this,
254                    StartConversationActivity.class);
255            contactsIntent.setFlags(
256                    // if activity exists in stack, pop the stack and go back to it
257                    Intent.FLAG_ACTIVITY_CLEAR_TOP |
258                            // otherwise, make a new task for it
259                            Intent.FLAG_ACTIVITY_NEW_TASK |
260                            // don't use the new activity animation; finish
261                            // animation runs instead
262                            Intent.FLAG_ACTIVITY_NO_ANIMATION);
263            startActivity(contactsIntent);
264            finish();
265            return true;
266        } else {
267            return super.onNavigateUp();
268        }
269    }
270
271    @Override
272    public void onClickTglAccountState(Account account, boolean enable) {
273        if (enable) {
274            enableAccount(account);
275        } else {
276            disableAccount(account);
277        }
278    }
279
280    private void addAccountFromKey() {
281        try {
282            KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null);
283        } catch (ActivityNotFoundException e) {
284            Toast.makeText(this, R.string.device_does_not_support_certificates, Toast.LENGTH_LONG).show();
285        }
286    }
287
288    private void publishAvatar(Account account) {
289        Intent intent = new Intent(getApplicationContext(),
290                PublishProfilePictureActivity.class);
291        intent.putExtra(EXTRA_ACCOUNT, account.getJid().toString());
292        startActivity(intent);
293    }
294
295    private void disableAllAccounts() {
296        List<Account> list = new ArrayList<>();
297        synchronized (this.accountList) {
298            for (Account account : this.accountList) {
299                if (account.isEnabled()) {
300                    list.add(account);
301                }
302            }
303        }
304        for (Account account : list) {
305            disableAccount(account);
306        }
307    }
308
309    private boolean accountsLeftToDisable() {
310        synchronized (this.accountList) {
311            for (Account account : this.accountList) {
312                if (account.isEnabled()) {
313                    return true;
314                }
315            }
316            return false;
317        }
318    }
319
320    private boolean accountsLeftToEnable() {
321        synchronized (this.accountList) {
322            for (Account account : this.accountList) {
323                if (!account.isEnabled()) {
324                    return true;
325                }
326            }
327            return false;
328        }
329    }
330
331    private void enableAllAccounts() {
332        List<Account> list = new ArrayList<>();
333        synchronized (this.accountList) {
334            for (Account account : this.accountList) {
335                if (!account.isEnabled()) {
336                    list.add(account);
337                }
338            }
339        }
340        for (Account account : list) {
341            enableAccount(account);
342        }
343    }
344
345    private void disableAccount(Account account) {
346        account.setOption(Account.OPTION_DISABLED, true);
347        if (!xmppConnectionService.updateAccount(account)) {
348            Toast.makeText(this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show();
349        }
350    }
351
352    private void enableAccount(Account account) {
353        account.setOption(Account.OPTION_DISABLED, false);
354        final XmppConnection connection = account.getXmppConnection();
355        if (connection != null) {
356            connection.resetEverything();
357        }
358        if (!xmppConnectionService.updateAccount(account)) {
359            Toast.makeText(this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show();
360        }
361    }
362
363    private void publishOpenPGPPublicKey(Account account) {
364        if (ManageAccountActivity.this.hasPgp()) {
365            announcePgp(selectedAccount, null, null, onOpenPGPKeyPublished);
366        } else {
367            this.showInstallPgpDialog();
368        }
369    }
370
371    private void deleteAccount(final Account account) {
372        AlertDialog.Builder builder = new AlertDialog.Builder(this);
373        builder.setTitle(getString(R.string.mgmt_account_are_you_sure));
374        builder.setIconAttribute(android.R.attr.alertDialogIcon);
375        builder.setMessage(getString(R.string.mgmt_account_delete_confirm_text));
376        builder.setPositiveButton(getString(R.string.delete),
377                (dialog, which) -> {
378                    xmppConnectionService.deleteAccount(account);
379                    selectedAccount = null;
380                    if (xmppConnectionService.getAccounts().size() == 0 && Config.MAGIC_CREATE_DOMAIN != null) {
381                        WelcomeActivity.launch(this);
382                    }
383                });
384        builder.setNegativeButton(getString(R.string.cancel), null);
385        builder.create().show();
386    }
387
388    @Override
389    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
390        super.onActivityResult(requestCode, resultCode, data);
391        if (resultCode == RESULT_OK) {
392            if (xmppConnectionServiceBound) {
393                if (requestCode == REQUEST_CHOOSE_PGP_ID) {
394                    if (data.getExtras().containsKey(OpenPgpApi.EXTRA_SIGN_KEY_ID)) {
395                        selectedAccount.setPgpSignId(data.getExtras().getLong(OpenPgpApi.EXTRA_SIGN_KEY_ID));
396                        announcePgp(selectedAccount, null, null, onOpenPGPKeyPublished);
397                    } else {
398                        choosePgpSignId(selectedAccount);
399                    }
400                } else if (requestCode == REQUEST_ANNOUNCE_PGP) {
401                    announcePgp(selectedAccount, null, data, onOpenPGPKeyPublished);
402                }
403                this.mPostponedActivityResult = null;
404            } else {
405                this.mPostponedActivityResult = new Pair<>(requestCode, data);
406            }
407        }
408    }
409
410    @Override
411    public void alias(String alias) {
412        if (alias != null) {
413            xmppConnectionService.createAccountFromKey(alias, this);
414        }
415    }
416
417    @Override
418    public void onAccountCreated(Account account) {
419        Intent intent = new Intent(this, EditAccountActivity.class);
420        intent.putExtra("jid", account.getJid().asBareJid().toString());
421        intent.putExtra("init", true);
422        startActivity(intent);
423    }
424
425    @Override
426    public void informUser(final int r) {
427        runOnUiThread(() -> Toast.makeText(ManageAccountActivity.this, r, Toast.LENGTH_LONG).show());
428    }
429}