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