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