ManageAccountActivity.java

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