fix crash when clicking invisible footer

Phillip Davis created

the stack trace manifested as:
```
Version: Cheogram debug-local+free
Manufacturer: OnePlus
Device: OnePlus6
Timestamp: 2026-04-01 21:27:32 -0400
java.lang.IndexOutOfBoundsException: Index 1 out of bounds for length 1
	at jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
	at jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70)
	at jdk.internal.util.Preconditions.checkIndex(Preconditions.java:266)
	at java.util.Objects.checkIndex(Objects.java:385)
	at java.util.ArrayList.get(ArrayList.java:434)
	at eu.siacs.conversations.ui.ManageAccountActivity.lambda$onCreate$3(ManageAccountActivity.java:140)
	at eu.siacs.conversations.ui.ManageAccountActivity.$r8$lambda$fcEx2bGWBsQPp04OkU82pOGEvUg(Unknown Source:0)
	at eu.siacs.conversations.ui.ManageAccountActivity$$ExternalSyntheticLambda0.onItemClick(D8$$SyntheticClass:0)
	at android.widget.AdapterView.performItemClick(AdapterView.java:330)
	at android.widget.AbsListView.performItemClick(AbsListView.java:1274)
	at android.widget.AbsListView.onKeyUp(AbsListView.java:3511)
	at android.widget.ListView.commonKey(ListView.java:2498)
	at android.widget.ListView.onKeyUp(ListView.java:2371)
	at android.view.KeyEvent.dispatch(KeyEvent.java:3471)
	at android.view.View.dispatchKeyEvent(View.java:16521)
	at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1982)
	at android.widget.ListView.dispatchKeyEvent(ListView.java:2346)
	at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1987)
	at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1987)
	at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1987)
	at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1987)
	at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1987)
	at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1987)
	at com.android.internal.policy.DecorView.superDispatchKeyEvent(DecorView.java:462)
	at com.android.internal.policy.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1971)
	at android.app.Activity.dispatchKeyEvent(Activity.java:4557)
	at androidx.core.app.ComponentActivity.superDispatchKeyEvent(ComponentActivity.kt:96)
	at androidx.core.view.KeyEventDispatcher.dispatchKeyEvent(KeyEventDispatcher.java:85)
	at androidx.core.app.ComponentActivity.dispatchKeyEvent(ComponentActivity.kt:110)
	at androidx.appcompat.app.AppCompatActivity.dispatchKeyEvent(AppCompatActivity.java:604)
	at androidx.appcompat.view.WindowCallbackWrapper.dispatchKeyEvent(WindowCallbackWrapper.java:59)
	at androidx.appcompat.app.AppCompatDelegateImpl$AppCompatWindowCallback.dispatchKeyEvent(AppCompatDelegateImpl.java:3397)
	at com.android.internal.policy.DecorView.dispatchKeyEvent(DecorView.java:376)
	at android.view.ViewRootImpl$ViewPostImeInputStage.processKeyEvent(ViewRootImpl.java:8034)
	at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:7873)
	at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:7259)
	at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:7316)
	at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:7282)
	at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:7448)
	at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:7290)
	at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:7505)
	at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:7263)
	at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:7316)
	at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:7282)
	at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:7290)
	at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:7263)
	at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:7316)
	at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:7282)
	at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:7481)
	at android.view.ViewRootImpl$ImeInputStage.onFinishedInputEvent(ViewRootImpl.java:7721)
	at android.view.inputmethod.InputMethodManager$PendingEvent.run(InputMethodManager.java:5053)
	at android.view.inputmethod.InputMethodManager.invokeFinishedInputEventCallback(InputMethodManager.java:4430)
	at android.view.inputmethod.InputMethodManager.finishedInputEvent(InputMethodManager.java:4421)
	at android.view.inputmethod.InputMethodManager.-$$Nest$mfinishedInputEvent(Unknown Source:0)
	at android.view.inputmethod.InputMethodManager$ImeInputEventSender.onInputEventFinished(InputMethodManager.java:5030)
	at android.view.InputEventSender.dispatchInputEventFinished(InputEventSender.java:181)
	at android.os.MessageQueue.nativePollOnce(Native Method)
	at android.os.MessageQueue.next(MessageQueue.java:387)
	at android.os.Looper.loopOnce(Looper.java:189)
	at android.os.Looper.loop(Looper.java:317)
	at android.app.ActivityThread.main(ActivityThread.java:8934)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:591)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:911)
```
notice that this required tab navigation, as the footer doesn't seem
visible, although maybe possible to hit it with your thumb idk. this is
especially tricky when the footer is invisible, i.e., when there aren't
any PSTN gateways configured

Change summary

src/cheogram/java/eu/siacs/conversations/ui/ManageAccountActivity.java | 11 
src/test/java/eu/siacs/conversations/ui/ManageAccountActivityTest.java | 66 
2 files changed, 75 insertions(+), 2 deletions(-)

Detailed changes

src/cheogram/java/eu/siacs/conversations/ui/ManageAccountActivity.java 🔗

@@ -136,7 +136,12 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
         accountListView = binding.accountList;
         this.mAccountAdapter = new AccountAdapter(this, accountList);
         accountListView.setAdapter(this.mAccountAdapter);
-        accountListView.setOnItemClickListener((arg0, view, position, arg3) -> switchToAccount(accountList.get(position)));
+        accountListView.setOnItemClickListener((arg0, view, position, arg3) -> {
+            final Object item = arg0.getItemAtPosition(position);
+            if (item != null) {
+                switchToAccount((Account) item);
+            }
+        });
 
 
         LayoutInflater inflater = getLayoutInflater();
@@ -164,7 +169,9 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
         ManageAccountActivity.this.getMenuInflater().inflate(
                 R.menu.manageaccounts_context, menu);
         AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
-        this.selectedAccount = accountList.get(acmi.position);
+        final Object item = accountListView.getItemAtPosition(acmi.position);
+        if (item == null) return;
+        this.selectedAccount = (Account) item;
         if (this.selectedAccount.isEnabled()) {
             menu.findItem(R.id.mgmt_account_enable).setVisible(false);
             menu.findItem(R.id.mgmt_account_announce_pgp).setVisible(Config.supportOpenPgp());

src/test/java/eu/siacs/conversations/ui/ManageAccountActivityTest.java 🔗

@@ -0,0 +1,66 @@
+package eu.siacs.conversations.ui;
+
+import android.content.Intent;
+import android.os.Build;
+import android.widget.AdapterView;
+import android.widget.ListView;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.ConscryptMode;
+import org.robolectric.shadows.ShadowActivity;
+
+import eu.siacs.conversations.Conversations;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.xmpp.Jid;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = Build.VERSION_CODES.TIRAMISU, application = Conversations.class)
+@ConscryptMode(ConscryptMode.Mode.OFF)
+public class ManageAccountActivityTest {
+
+    @Test
+    public void onItemClickNavigatesToCorrectAccountAndIgnoresFooter() {
+        ManageAccountActivity activity =
+                Robolectric.buildActivity(ManageAccountActivity.class).create().get();
+
+        Account[] accounts = new Account[] {
+            new Account(Jid.ofLocalAndDomain("alice", "example.org"), "password"),
+            new Account(Jid.ofLocalAndDomain("bob", "example.org"), "password"),
+            new Account(Jid.ofLocalAndDomain("carol", "example.org"), "password"),
+        };
+        for (Account account : accounts) {
+            activity.accountList.add(account);
+        }
+        activity.mAccountAdapter.notifyDataSetChanged();
+
+        ListView listView = activity.accountListView;
+        AdapterView.OnItemClickListener listener = listView.getOnItemClickListener();
+        ShadowActivity shadow = Shadows.shadowOf(activity);
+
+        for (int position = 0; position < accounts.length; position++) {
+            listener.onItemClick(listView, null, position, 0);
+            Intent started = shadow.getNextStartedActivity();
+            Assert.assertNotNull("position " + position + ": should start activity", started);
+            Assert.assertEquals(
+                    "position " + position + ": should navigate to correct account",
+                    accounts[position].getJid().asBareJid().toString(),
+                    started.getStringExtra("jid"));
+        }
+
+        int footerPosition = accounts.length;
+        Assert.assertEquals(
+                "position " + footerPosition + ": should be a header/footer view type",
+                AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER,
+                listView.getAdapter().getItemViewType(footerPosition));
+        listener.onItemClick(listView, null, footerPosition, 0);
+        Assert.assertNull(
+                "position " + footerPosition + " (footer): should not start activity",
+                shadow.getNextStartedActivity());
+    }
+}