From 07629873d5386d28e9d279f197a3782e19378363 Mon Sep 17 00:00:00 2001 From: "Eric C. Johnson" Date: Mon, 16 Feb 2026 16:17:24 -0700 Subject: [PATCH 1/2] @W-20798759: [MSDK] Add ability to programmatically update servers list --- .../androidsdk/config/LoginServerManager.java | 94 ++++++++++++++++++- .../auth/LoginServerManagerTest.java | 90 +++++++++++++++++- 2 files changed, 181 insertions(+), 3 deletions(-) diff --git a/libs/SalesforceSDK/src/com/salesforce/androidsdk/config/LoginServerManager.java b/libs/SalesforceSDK/src/com/salesforce/androidsdk/config/LoginServerManager.java index 467b0880f3..42a0a7937a 100644 --- a/libs/SalesforceSDK/src/com/salesforce/androidsdk/config/LoginServerManager.java +++ b/libs/SalesforceSDK/src/com/salesforce/androidsdk/config/LoginServerManager.java @@ -26,6 +26,9 @@ */ package com.salesforce.androidsdk.config; +import static java.lang.String.format; +import static java.util.Locale.US; + import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; @@ -130,7 +133,7 @@ public LoginServer getSelectedLoginServer() { if (name != null && url != null) { LoginServer server = new LoginServer(name, url, isCustom); - // Only notify livedata consumers if the value has changed. + // Only notify live data consumers if the value has changed. if (!server.equals(selectedServer.getValue())) { selectedServer.postValue(server); } @@ -323,6 +326,95 @@ public List getLoginServersFromPreferences() { return getLoginServersFromPreferences(settings); } + /** + * Reorders a custom login server in the list of login servers. + * + * @param originalIndex The original index of the custom login server. If this is not the index + * of a custom login server, this method will do nothing + * @param updatedIndex The new index of the custom login server. This must be after the last + * non-custom login server and within the updatable bounds of the list. If + * it is not it will be automatically corrected + */ + @SuppressWarnings("unused") + public void reorderCustomLoginServer( + final int originalIndex, + int updatedIndex + ) { + // Get the login server at the original index. + final List loginServers = getLoginServers(); + final LoginServer originalLoginServer = loginServers.get(originalIndex); + + // Guard against reordering a non-custom login server. + if (!originalLoginServer.isCustom) { + return; + } + + // Determine the last non-custom login server index. + final List servers = getLoginServers(); + int lastNonCustomIndex = -1; + for (int i = servers.size() - 1; i >= 0; i--) { + if (!servers.get(i).isCustom) { + lastNonCustomIndex = i; + break; + } + } + + // Adjust the re-ordered custom login server index to be within bounds. + if (updatedIndex <= lastNonCustomIndex) { + updatedIndex = lastNonCustomIndex + 1; + } else if (updatedIndex >= servers.size()) { + updatedIndex = servers.size() - 1; + } + + // Update the login server list. + loginServers.remove(originalIndex); + loginServers.add(updatedIndex, originalLoginServer); + + // Edit each login server indexed after the updated index. + final Editor editor = settings.edit(); + for (int i = updatedIndex; i < loginServers.size(); i++) { + final LoginServer loginServer = loginServers.get(i); + editor.remove(format(US, SERVER_NAME, i)) + .remove(format(US, SERVER_URL, i)) + .remove(format(US, IS_CUSTOM, i)) + .putString(format(US, SERVER_NAME, i), loginServer.name) + .putString(format(US, SERVER_URL, i), loginServer.url) + .putBoolean(format(US, IS_CUSTOM, i), loginServer.isCustom); + } + editor.apply(); + } + + /** + * Replaces one custom login server with another. + * + * @param originalCustomLoginServer The original custom login server. If this is not a custom + * login server or doesn't match an existing login server this + * method will do nothing. + * @param updatedCustomLoginServer The updated custom login server. If this is not a custom + * login server this method will do nothing. + */ + @SuppressWarnings("unused") + public void replaceCustomLoginServer( + final LoginServer originalCustomLoginServer, + final LoginServer updatedCustomLoginServer + ) { + // Guard against replacing a non-custom login server. + if (!originalCustomLoginServer.isCustom || !updatedCustomLoginServer.isCustom) { + return; + } + + final int originalIndex = getLoginServers().indexOf(originalCustomLoginServer); + + // Guard against an original login server that doesn't exist. + if (originalIndex == -1) { + return; + } + + removeServer(originalCustomLoginServer); + addCustomLoginServer(updatedCustomLoginServer.name, updatedCustomLoginServer.url); + reorderCustomLoginServer(getLoginServers().size() - 1, originalIndex); + } + /** * Returns production and sandbox as the login servers * (only called when servers.xml is missing). diff --git a/libs/test/SalesforceSDKTest/src/com/salesforce/androidsdk/auth/LoginServerManagerTest.java b/libs/test/SalesforceSDKTest/src/com/salesforce/androidsdk/auth/LoginServerManagerTest.java index 0637ec098d..43bc3bcc98 100644 --- a/libs/test/SalesforceSDKTest/src/com/salesforce/androidsdk/auth/LoginServerManagerTest.java +++ b/libs/test/SalesforceSDKTest/src/com/salesforce/androidsdk/auth/LoginServerManagerTest.java @@ -31,9 +31,9 @@ import android.content.Context; import androidx.arch.core.executor.testing.InstantTaskExecutorRule; -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.filters.SmallTest; import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; import com.salesforce.androidsdk.TestForceApp; import com.salesforce.androidsdk.app.SalesforceSDKManager; @@ -347,6 +347,92 @@ public void testAddingDuplicateServers() { loginServerManager.getLoginServers().size()); } + /** + * Test both replace and re-order custom login server. + */ + @Test + public void testReplaceAndReOrderCustomLoginServer() { + + // Test data. + final String originalName = "ORIGINAL_CUSTOM_LOGIN_SERVER_FOR_REPLACEMENT_TEST"; + final String originalUrl = "https://original.example.com"; + final LoginServer originalCustomLoginServer = new LoginServer( + originalName, + originalUrl, + true + ); + final String otherName = "OTHER_CUSTOM_LOGIN_SERVER_FOR_REPLACEMENT_TEST"; + final String otherUrl = "https://other.example.com"; + final LoginServer otherCustomLoginServer = new LoginServer( + otherName, + otherUrl, + true + ); + final String updatedName = "UPDATED_CUSTOM_LOGIN_SERVER_FOR_REPLACEMENT_TEST"; + final String updatedUrl = "https://updated.example.com"; + final LoginServer updatedCustomLoginServer = new LoginServer( + updatedName, + updatedUrl, + true + ); + final String nonCustomName = "NON_CUSTOM_LOGIN_SERVER_FOR_REPLACEMENT_TEST"; + final String nonCustomUrl = "https://non.custom.example.com"; + final LoginServer nonCustomLoginServer = new LoginServer( + nonCustomName, + nonCustomUrl, + false + ); + + // Verify the original and other custom login servers are not present. + Assert.assertFalse(loginServerManager.getLoginServers().contains(originalCustomLoginServer)); + Assert.assertFalse(loginServerManager.getLoginServers().contains(otherCustomLoginServer)); + + // Add the original and other custom login server. + loginServerManager.addCustomLoginServer(originalName, originalUrl); + loginServerManager.addCustomLoginServer(otherName, otherUrl); + + // Verify the original and other custom login servers were added. + Assert.assertEquals(originalCustomLoginServer, loginServerManager.getLoginServers().get(loginServerManager.getLoginServers().size() - 2)); + Assert.assertEquals(otherCustomLoginServer, loginServerManager.getLoginServers().get(loginServerManager.getLoginServers().size() - 1)); + + // Prepare for negative tests. + final LoginServer production = new LoginServer("Production", "https://login.salesforce.com", false); + final LoginServer productionMismatch = new LoginServer("Production", "https://login.salesforce.com", true); + final LoginServer productionReplacement = new LoginServer("Production Replaced", "https://login.salesforce.com", false); + + // Attempt the prohibited replacement of a non-custom login server where the original matches. + loginServerManager.replaceCustomLoginServer(production, productionReplacement); + Assert.assertTrue(loginServerManager.getLoginServers().contains(production)); + Assert.assertFalse(loginServerManager.getLoginServers().contains(productionReplacement)); + + // Attempt the prohibited replacement of a non-custom login server where the original doesn't exit. + loginServerManager.replaceCustomLoginServer(productionMismatch, productionReplacement); + Assert.assertTrue(loginServerManager.getLoginServers().contains(production)); + Assert.assertFalse(loginServerManager.getLoginServers().contains(productionReplacement)); + + // Attempt the prohibited reordering of a non-custom login server. + loginServerManager.reorderCustomLoginServer(0, 1); + Assert.assertEquals(loginServerManager.getLoginServers().get(0), production); + + // Replace the original custom login server with a non-custom server. + loginServerManager.replaceCustomLoginServer(originalCustomLoginServer, nonCustomLoginServer); + + // Verify the original and other custom login servers weren't changed. + Assert.assertFalse(loginServerManager.getLoginServers().contains(nonCustomLoginServer)); + Assert.assertEquals(originalCustomLoginServer, loginServerManager.getLoginServers().get(loginServerManager.getLoginServers().size() - 2)); + Assert.assertEquals(otherCustomLoginServer, loginServerManager.getLoginServers().get(loginServerManager.getLoginServers().size() - 1)); + + // Replace the original custom login server. + loginServerManager.replaceCustomLoginServer(originalCustomLoginServer, updatedCustomLoginServer); + + // Verify the original custom login server is not present. + Assert.assertFalse(loginServerManager.getLoginServers().contains(originalCustomLoginServer)); + + // Verify the updated and other custom login servers are present. + Assert.assertEquals(updatedCustomLoginServer, loginServerManager.getLoginServers().get(loginServerManager.getLoginServers().size() - 2)); + Assert.assertEquals(otherCustomLoginServer, loginServerManager.getLoginServers().get(loginServerManager.getLoginServers().size() - 1)); + } + private void assertProduction(LoginServer server) { Assert.assertEquals("Expected production's name", "Production", server.name); Assert.assertEquals("Expected production's url", PRODUCTION_URL, server.url); From 3e8734ba83cdc194041b0f18984c3c46a9568b78 Mon Sep 17 00:00:00 2001 From: "Eric C. Johnson" Date: Tue, 17 Feb 2026 16:49:56 -0700 Subject: [PATCH 2/2] @W-20798759: [MSDK] Add ability to programmatically update servers list (Code Coverage 100%) --- .../androidsdk/config/LoginServerManager.java | 11 +++--- .../auth/LoginServerManagerTest.java | 38 ++++++++++++++++++- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/libs/SalesforceSDK/src/com/salesforce/androidsdk/config/LoginServerManager.java b/libs/SalesforceSDK/src/com/salesforce/androidsdk/config/LoginServerManager.java index 42a0a7937a..bc38f9f71d 100644 --- a/libs/SalesforceSDK/src/com/salesforce/androidsdk/config/LoginServerManager.java +++ b/libs/SalesforceSDK/src/com/salesforce/androidsdk/config/LoginServerManager.java @@ -351,17 +351,16 @@ public void reorderCustomLoginServer( // Determine the last non-custom login server index. final List servers = getLoginServers(); - int lastNonCustomIndex = -1; + int firstCustomLoginServerIndex = -1; for (int i = servers.size() - 1; i >= 0; i--) { - if (!servers.get(i).isCustom) { - lastNonCustomIndex = i; - break; + if (servers.get(i).isCustom) { + firstCustomLoginServerIndex = i; } } // Adjust the re-ordered custom login server index to be within bounds. - if (updatedIndex <= lastNonCustomIndex) { - updatedIndex = lastNonCustomIndex + 1; + if (updatedIndex <= firstCustomLoginServerIndex) { + updatedIndex = firstCustomLoginServerIndex; } else if (updatedIndex >= servers.size()) { updatedIndex = servers.size() - 1; } diff --git a/libs/test/SalesforceSDKTest/src/com/salesforce/androidsdk/auth/LoginServerManagerTest.java b/libs/test/SalesforceSDKTest/src/com/salesforce/androidsdk/auth/LoginServerManagerTest.java index 43bc3bcc98..f0cd848973 100644 --- a/libs/test/SalesforceSDKTest/src/com/salesforce/androidsdk/auth/LoginServerManagerTest.java +++ b/libs/test/SalesforceSDKTest/src/com/salesforce/androidsdk/auth/LoginServerManagerTest.java @@ -387,6 +387,7 @@ public void testReplaceAndReOrderCustomLoginServer() { Assert.assertFalse(loginServerManager.getLoginServers().contains(originalCustomLoginServer)); Assert.assertFalse(loginServerManager.getLoginServers().contains(otherCustomLoginServer)); + // Add the original and other custom login server. loginServerManager.addCustomLoginServer(originalName, originalUrl); loginServerManager.addCustomLoginServer(otherName, otherUrl); @@ -395,25 +396,30 @@ public void testReplaceAndReOrderCustomLoginServer() { Assert.assertEquals(originalCustomLoginServer, loginServerManager.getLoginServers().get(loginServerManager.getLoginServers().size() - 2)); Assert.assertEquals(otherCustomLoginServer, loginServerManager.getLoginServers().get(loginServerManager.getLoginServers().size() - 1)); + // Prepare for negative tests. final LoginServer production = new LoginServer("Production", "https://login.salesforce.com", false); - final LoginServer productionMismatch = new LoginServer("Production", "https://login.salesforce.com", true); + final LoginServer productionMismatch = new LoginServer("Production?", "https://login.salesforce.com", true); final LoginServer productionReplacement = new LoginServer("Production Replaced", "https://login.salesforce.com", false); + final LoginServer productionReplacementMismatch = new LoginServer("Production Replaced?", "https://login.salesforce.com", true); // Attempt the prohibited replacement of a non-custom login server where the original matches. loginServerManager.replaceCustomLoginServer(production, productionReplacement); Assert.assertTrue(loginServerManager.getLoginServers().contains(production)); Assert.assertFalse(loginServerManager.getLoginServers().contains(productionReplacement)); + // Attempt the prohibited replacement of a non-custom login server where the original doesn't exit. - loginServerManager.replaceCustomLoginServer(productionMismatch, productionReplacement); + loginServerManager.replaceCustomLoginServer(productionMismatch, productionReplacementMismatch); Assert.assertTrue(loginServerManager.getLoginServers().contains(production)); Assert.assertFalse(loginServerManager.getLoginServers().contains(productionReplacement)); + // Attempt the prohibited reordering of a non-custom login server. loginServerManager.reorderCustomLoginServer(0, 1); Assert.assertEquals(loginServerManager.getLoginServers().get(0), production); + // Replace the original custom login server with a non-custom server. loginServerManager.replaceCustomLoginServer(originalCustomLoginServer, nonCustomLoginServer); @@ -422,6 +428,7 @@ public void testReplaceAndReOrderCustomLoginServer() { Assert.assertEquals(originalCustomLoginServer, loginServerManager.getLoginServers().get(loginServerManager.getLoginServers().size() - 2)); Assert.assertEquals(otherCustomLoginServer, loginServerManager.getLoginServers().get(loginServerManager.getLoginServers().size() - 1)); + // Replace the original custom login server. loginServerManager.replaceCustomLoginServer(originalCustomLoginServer, updatedCustomLoginServer); @@ -431,6 +438,33 @@ public void testReplaceAndReOrderCustomLoginServer() { // Verify the updated and other custom login servers are present. Assert.assertEquals(updatedCustomLoginServer, loginServerManager.getLoginServers().get(loginServerManager.getLoginServers().size() - 2)); Assert.assertEquals(otherCustomLoginServer, loginServerManager.getLoginServers().get(loginServerManager.getLoginServers().size() - 1)); + + // Attempt to move the updated custom login server above the non-custom login servers. + loginServerManager.reorderCustomLoginServer(loginServerManager.getLoginServers().indexOf(updatedCustomLoginServer), 0); + + // Verify the updated custom login server is actually immediately following the last non-custom login server. + final List loginServers = loginServerManager.getLoginServers(); + int lastNonCustomIndex = -1; + for (int i = 0; i < loginServers.size(); i++) { + final LoginServer loginServer = loginServers.get(i); + if (!loginServer.isCustom) { + lastNonCustomIndex = i; + } + } + Assert.assertEquals(loginServers.get(lastNonCustomIndex + 1), updatedCustomLoginServer); + + + // Attempt to move the updated custom login server one greater than the upper bounds of the login servers list. + loginServerManager.reorderCustomLoginServer(loginServerManager.getLoginServers().indexOf(updatedCustomLoginServer), loginServerManager.getLoginServers().size()); + + // Attempt to move the updated custom login server more than one greater than the upper bounds of the login servers list. + loginServerManager.reorderCustomLoginServer(loginServerManager.getLoginServers().indexOf(updatedCustomLoginServer), loginServerManager.getLoginServers().size() + 1); + + // Attempt to move the updated custom login server more than one less than the upper bounds of the login servers list. + loginServerManager.reorderCustomLoginServer(loginServerManager.getLoginServers().indexOf(updatedCustomLoginServer), loginServerManager.getLoginServers().size() - 1); + + // Verify the updated custom login server is now the last login server in the list. + Assert.assertEquals(loginServerManager.getLoginServers().getLast(), updatedCustomLoginServer); } private void assertProduction(LoginServer server) {