diff --git a/android/clientlib/src/main/java/com/solana/mobilewalletadapter/clientlib/protocol/MobileWalletAdapterClient.java b/android/clientlib/src/main/java/com/solana/mobilewalletadapter/clientlib/protocol/MobileWalletAdapterClient.java index 9ff387343..846a1d3ab 100644 --- a/android/clientlib/src/main/java/com/solana/mobilewalletadapter/clientlib/protocol/MobileWalletAdapterClient.java +++ b/android/clientlib/src/main/java/com/solana/mobilewalletadapter/clientlib/protocol/MobileWalletAdapterClient.java @@ -145,6 +145,8 @@ public static class AuthorizationResult { public final String accountLabel; @Nullable public final Uri walletUriBase; + @Nullable + public final Uri walletIcon; @NonNull @Size(min = 1) public final AuthorizedAccount[] accounts; @Nullable @@ -153,8 +155,10 @@ public static class AuthorizationResult { private AuthorizationResult(@NonNull String authToken, @NonNull @Size(min = 1) AuthorizedAccount[] accounts, @Nullable Uri walletUriBase, + @Nullable Uri walletIcon, @Nullable SignInResult signInResult) { this.authToken = authToken; + this.walletIcon = walletIcon; this.walletUriBase = walletUriBase; this.accounts = accounts; this.signInResult = signInResult; @@ -174,7 +178,7 @@ public String toString() { @NonNull public AuthorizationResult with(SignInResult signInResult) { - return new AuthorizationResult(authToken, accounts, walletUriBase, signInResult); + return new AuthorizationResult(authToken, accounts, walletUriBase, walletIcon, signInResult); } public static class AuthorizedAccount { @@ -247,7 +251,7 @@ public static AuthorizationResult create( Uri walletUriBase ) { AuthorizedAccount[] accounts = new AuthorizedAccount[] { new AuthorizedAccount(publicKey, accountLabel, null, null) }; - return new AuthorizationResult(authToken, accounts, walletUriBase, null); + return new AuthorizationResult(authToken, accounts, walletUriBase, null, null); } @TestOnly @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) @@ -256,7 +260,7 @@ public static AuthorizationResult create( AuthorizedAccount[] accounts, Uri walletUriBase ) { - return new AuthorizationResult(authToken, accounts, walletUriBase, null); + return new AuthorizationResult(authToken, accounts, walletUriBase, null, null); } } @@ -331,6 +335,10 @@ protected AuthorizationResult processResult(@Nullable Object o) walletUriBaseStr + "'; expected a 'https' URI"); } + final String walletIconStr = jo.has(ProtocolContract.RESULT_WALLET_ICON) ? + jo.optString(ProtocolContract.RESULT_WALLET_ICON) : null; + final Uri walletIcon = walletIconStr != null ? Uri.parse(walletIconStr) : null; + final JSONObject signInResultJson = jo.has(ProtocolContract.RESULT_SIGN_IN) ? jo.optJSONObject(ProtocolContract.RESULT_SIGN_IN) : null; final AuthorizationResult.SignInResult signInResult; @@ -360,7 +368,7 @@ protected AuthorizationResult processResult(@Nullable Object o) signInResult = null; } - return new AuthorizationResult(authToken, authorizedAccounts, walletUriBase, signInResult); + return new AuthorizationResult(authToken, authorizedAccounts, walletUriBase, walletIcon, signInResult); } @Override diff --git a/android/common/src/main/java/com/solana/mobilewalletadapter/common/ProtocolContract.java b/android/common/src/main/java/com/solana/mobilewalletadapter/common/ProtocolContract.java index 5cd574fd3..b25c26e64 100644 --- a/android/common/src/main/java/com/solana/mobilewalletadapter/common/ProtocolContract.java +++ b/android/common/src/main/java/com/solana/mobilewalletadapter/common/ProtocolContract.java @@ -88,6 +88,7 @@ public class ProtocolContract { // RESULT_ACCOUNTS optionally includes a RESULT_SUPPORTED_FEATURES public static final String RESULT_WALLET_URI_BASE = "wallet_uri_base"; // type: String (absolute URI) + public static final String RESULT_WALLET_ICON = "wallet_icon"; // type: String (data URI) public static final String RESULT_SIGN_IN = "sign_in_result"; // type JSON object public static final String RESULT_SIGN_IN_ADDRESS = "address"; // type: String (address) diff --git a/android/walletlib/src/main/java/com/solana/mobilewalletadapter/walletlib/authorization/AuthRepositoryImpl.java b/android/walletlib/src/main/java/com/solana/mobilewalletadapter/walletlib/authorization/AuthRepositoryImpl.java index 78cb9a961..cb0910234 100644 --- a/android/walletlib/src/main/java/com/solana/mobilewalletadapter/walletlib/authorization/AuthRepositoryImpl.java +++ b/android/walletlib/src/main/java/com/solana/mobilewalletadapter/walletlib/authorization/AuthRepositoryImpl.java @@ -410,9 +410,9 @@ public AuthRecord issue(@NonNull String name, // If no matching account exists, create one if (accountRecordQueried == null) { accountId = (int) mAccountsDao.insert(authRecordId, account.publicKey, - account.accountLabel, account.icon, account.chains, account.features); + account.accountLabel, account.accountIcon, account.chains, account.features); accountRecord = new AccountRecord(accountId, authRecordId, account.publicKey, - account.accountLabel, account.icon, account.chains, account.features); + account.accountLabel, account.accountIcon, account.chains, account.features); } else { accountRecord = accountRecordQueried; } diff --git a/android/walletlib/src/main/java/com/solana/mobilewalletadapter/walletlib/protocol/MobileWalletAdapterServer.java b/android/walletlib/src/main/java/com/solana/mobilewalletadapter/walletlib/protocol/MobileWalletAdapterServer.java index 7f7a5720d..07d4e912a 100644 --- a/android/walletlib/src/main/java/com/solana/mobilewalletadapter/walletlib/protocol/MobileWalletAdapterServer.java +++ b/android/walletlib/src/main/java/com/solana/mobilewalletadapter/walletlib/protocol/MobileWalletAdapterServer.java @@ -249,13 +249,14 @@ private void onAuthorizationComplete(@NonNull NotifyOnCompleteFuture mNoConnectionTimeoutHandler; private final ScheduledExecutorService mTimeoutExecutorService = @@ -84,6 +86,18 @@ protected LocalScenario(@NonNull Context context, @NonNull byte[] associationPublicKey, @NonNull PowerConfigProvider powerConfigProvider, @NonNull List associationProtocolVersions) { + this(context, mobileWalletAdapterConfig, authIssuerConfig, callbacks, associationPublicKey, + powerConfigProvider, associationProtocolVersions, new DefaultWalletIconProvider(context)); + } + + /*package*/ LocalScenario(@NonNull Context context, + @NonNull MobileWalletAdapterConfig mobileWalletAdapterConfig, + @NonNull AuthIssuerConfig authIssuerConfig, + @NonNull Callbacks callbacks, + @NonNull byte[] associationPublicKey, + @NonNull PowerConfigProvider powerConfigProvider, + @NonNull List associationProtocolVersions, + @NonNull WalletIconProvider iconProvider) { mCallbacks = callbacks; mMobileWalletAdapterConfig = mobileWalletAdapterConfig; this.associationProtocolVersions = associationProtocolVersions; @@ -97,6 +111,15 @@ protected LocalScenario(@NonNull Context context, mAuthRepository = new AuthRepositoryImpl(context, authIssuerConfig); this.mPowerManager = powerConfigProvider; + + Uri walletIcon = iconProvider.getWalletIconDataUri(); + if (walletIcon == null || + (walletIcon.getScheme() != null && walletIcon.getScheme().equals("data"))) { + this.mWalletIcon = walletIcon; + } else { + throw new IllegalArgumentException("wallet icon provider returned an invalid icon URI: " + + "the wallet icon must be a data URI"); + } } @Override @@ -230,7 +253,7 @@ public void authorize(@NonNull MobileWalletAdapterServer.AuthorizeRequest reques final String authToken = mAuthRepository.toAuthToken(authRecord); request.complete(new MobileWalletAdapterServer.AuthorizationResult(authToken, - authorize.accounts, authorize.walletUriBase, authorize.signInResult)); + authorize.accounts, authorize.walletUriBase, mWalletIcon, authorize.signInResult)); } else { request.completeExceptionally(new MobileWalletAdapterServer.RequestDeclinedException( "authorize request declined")); @@ -304,7 +327,7 @@ private void doReauthorize(@NonNull MobileWalletAdapterServer.AuthorizeRequest r mIoHandler.post(() -> request.complete( new MobileWalletAdapterServer.AuthorizationResult( authToken, authRecord.getAuthorizedAccounts(), - authRecord.walletUriBase, null))); + authRecord.walletUriBase, mWalletIcon, null))); } catch (ExecutionException e) { final Throwable cause = e.getCause(); assert(cause instanceof Exception); // expected to always be an Exception diff --git a/android/walletlib/src/main/java/com/solana/mobilewalletadapter/walletlib/scenario/LocalWebSocketServerScenario.java b/android/walletlib/src/main/java/com/solana/mobilewalletadapter/walletlib/scenario/LocalWebSocketServerScenario.java index 886e8104e..9abfc5b28 100644 --- a/android/walletlib/src/main/java/com/solana/mobilewalletadapter/walletlib/scenario/LocalWebSocketServerScenario.java +++ b/android/walletlib/src/main/java/com/solana/mobilewalletadapter/walletlib/scenario/LocalWebSocketServerScenario.java @@ -61,6 +61,21 @@ associationPublicKey, port, new DevicePowerConfigProvider(context), this.mWebSocketServer = new LocalWebSocketServer(this, mWebSocketServerCallbacks); } + /*package*/ LocalWebSocketServerScenario(@NonNull Context context, + @NonNull MobileWalletAdapterConfig mobileWalletAdapterConfig, + @NonNull AuthIssuerConfig authIssuerConfig, + @NonNull LocalScenario.Callbacks callbacks, + @NonNull byte[] associationPublicKey, + @WebSocketsTransportContract.LocalPortRange int port, + PowerConfigProvider powerConfigProvider, + @NonNull List associationProtocolVersions, + @NonNull WalletIconProvider iconProvider) { + super(context, mobileWalletAdapterConfig, authIssuerConfig, callbacks, associationPublicKey, + powerConfigProvider, associationProtocolVersions, iconProvider); + this.port = port; + this.mWebSocketServer = new LocalWebSocketServer(this, mWebSocketServerCallbacks); + } + @Override public void start() { if (mState != State.NOT_STARTED) { diff --git a/android/walletlib/src/main/java/com/solana/mobilewalletadapter/walletlib/scenario/WalletIconProvider.java b/android/walletlib/src/main/java/com/solana/mobilewalletadapter/walletlib/scenario/WalletIconProvider.java new file mode 100644 index 000000000..d7bfee1fe --- /dev/null +++ b/android/walletlib/src/main/java/com/solana/mobilewalletadapter/walletlib/scenario/WalletIconProvider.java @@ -0,0 +1,19 @@ +package com.solana.mobilewalletadapter.walletlib.scenario; + +import android.net.Uri; + +import androidx.annotation.Nullable; + +/*package*/ interface WalletIconProvider { + /** + * Returns a base64 encoded data URI of the wallet icon. This will be the icon dapps display + * for your wallet. The wallet icon must be an SVG, PNG, WebP, or GIF image. + *

+ * You can use a tool like https://base64.guru/converter/encode/image + * to encode an image using the "Data URI" setting. It's a good idea to compress your image + * losslessly with a tool like https://imageoptim.com first. + * + * @return a base64 encoded data URI of an SVG, PNG, WebP, or GIF image, or null + */ + @Nullable Uri getWalletIconDataUri(); +} diff --git a/android/walletlib/src/test/java/com/solana/mobilewalletadapter/walletlib/scenario/LocalWebSocketServerScenarioTest.java b/android/walletlib/src/test/java/com/solana/mobilewalletadapter/walletlib/scenario/LocalWebSocketServerScenarioTest.java index 8d0c08ce3..5188297e0 100644 --- a/android/walletlib/src/test/java/com/solana/mobilewalletadapter/walletlib/scenario/LocalWebSocketServerScenarioTest.java +++ b/android/walletlib/src/test/java/com/solana/mobilewalletadapter/walletlib/scenario/LocalWebSocketServerScenarioTest.java @@ -3,6 +3,7 @@ import static org.junit.Assert.*; import android.content.Context; +import android.net.Uri; import androidx.arch.core.executor.testing.InstantTaskExecutorRule; import androidx.test.core.app.ApplicationProvider; @@ -56,13 +57,14 @@ public void onLowPowerAndNoConnection() { }; PowerConfigProvider powerConfig = () -> false; + WalletIconProvider iconProvider = () -> null; List supportedVersions = List.of(SessionProperties.ProtocolVersion.LEGACY); // when new LocalWebSocketServerScenario(context, config, authConfig, lowPowerNoConnectionCallback, - publicKey, port, powerConfig, supportedVersions).start(); + publicKey, port, powerConfig, supportedVersions, iconProvider).start(); boolean lowPowerNoConnectionCallbackFired = latch.await(200, TimeUnit.MILLISECONDS); // then @@ -98,13 +100,14 @@ public void onLowPowerAndNoConnection() { }; PowerConfigProvider powerConfig = () -> true; + WalletIconProvider iconProvider = () -> null; List supportedVersions = List.of(SessionProperties.ProtocolVersion.LEGACY); // when new LocalWebSocketServerScenario(context, config, authConfig, lowPowerNoConnectionCallback, - publicKey, port, powerConfig, supportedVersions).start(); + publicKey, port, powerConfig, supportedVersions, iconProvider).start(); boolean lowPowerNoConnectionCallbackFired = latch.await(200, TimeUnit.MILLISECONDS); // then