ManageAccountActivity.java

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