From da335c208bd543b56b0daea3aae6d0e0ae1d1cc4 Mon Sep 17 00:00:00 2001 From: James Brown Date: Mon, 25 Mar 2024 16:41:34 +1100 Subject: [PATCH 1/4] - Fix for WalletConnect V2 Foreground Service --- app/src/main/java/com/alphawallet/app/C.java | 2 + .../app/interact/WalletConnectInteract.java | 21 ++-- .../app/service/WalletConnectV2Service.java | 44 ++++--- .../com/alphawallet/app/ui/HomeActivity.java | 16 ++- .../alphawallet/app/ui/WalletFragment.java | 4 +- .../app/ui/widget/adapter/TokensAdapter.java | 9 +- .../java/com/alphawallet/app/util/Utils.java | 14 ++- .../app/viewmodel/TokenFunctionViewModel.java | 2 +- .../walletconnect/AWWalletConnectClient.java | 114 +++++++----------- .../com/alphawallet/app/widget/TokenIcon.java | 5 - 10 files changed, 113 insertions(+), 118 deletions(-) diff --git a/app/src/main/java/com/alphawallet/app/C.java b/app/src/main/java/com/alphawallet/app/C.java index 505a088184..33b3136198 100644 --- a/app/src/main/java/com/alphawallet/app/C.java +++ b/app/src/main/java/com/alphawallet/app/C.java @@ -211,6 +211,7 @@ public abstract class C { public static final String APP_FOREGROUND_STATE = "com.alphawallet.APP_FOREGROUND_STATE"; public static final String EXTRA_APP_FOREGROUND = "com.alphawallet.IS_FOREGORUND"; public static final String QRCODE_SCAN = "com.alphawallet.QRSCAN"; + public static final String AWALLET_CODE = "com.alphawallet.AWALLET"; public static final String SIGNAL_NFT_SYNC = "com.alphawallet.SYNC_NFT"; public static final String SYNC_STATUS = "com.alphawallet.SYNC_STATUS"; @@ -278,6 +279,7 @@ public interface Key { public static final String DAPP_SUFFIX_RECEIVE = "receive"; public static final String DAPP_PREFIX_MAPS = "maps.google.com/maps?daddr="; public static final String DAPP_PREFIX_WALLETCONNECT = "wc"; + public static final String DAPP_PREFIX_AWALLET = "awallet"; public static final String ENS_SCAN_BLOCK = "ens_check_block"; public static final String ENS_HISTORY = "ensHistory"; diff --git a/app/src/main/java/com/alphawallet/app/interact/WalletConnectInteract.java b/app/src/main/java/com/alphawallet/app/interact/WalletConnectInteract.java index 55e75aae6c..dda05ee36c 100644 --- a/app/src/main/java/com/alphawallet/app/interact/WalletConnectInteract.java +++ b/app/src/main/java/com/alphawallet/app/interact/WalletConnectInteract.java @@ -2,7 +2,6 @@ import com.alphawallet.app.entity.walletconnect.WalletConnectSessionItem; import com.alphawallet.app.entity.walletconnect.WalletConnectV2SessionItem; -import com.alphawallet.app.service.RealmManager; import com.walletconnect.web3.wallet.client.Wallet; import com.walletconnect.web3.wallet.client.Web3Wallet; @@ -15,12 +14,10 @@ public class WalletConnectInteract { - private final RealmManager realmManager; - @Inject - public WalletConnectInteract(RealmManager realmManager) + public WalletConnectInteract() { - this.realmManager = realmManager; + } public int getSessionsCount() @@ -30,8 +27,7 @@ public int getSessionsCount() public List getSessions() { - List result = new ArrayList<>(); - result.addAll(getWalletConnectV2SessionItems()); + List result = new ArrayList<>(getWalletConnectV2SessionItems()); //now sort for active/newness result.sort((l, r) -> Long.compare(r.expiryTime, l.expiryTime)); @@ -39,6 +35,17 @@ public List getSessions() return result; } + public void fetchSessions(SessionFetchCallback sessionFetchCallback) + { + fetch(sessionFetchCallback); + } + + private void fetch(SessionFetchCallback sessionFetchCallback) + { + List result = new ArrayList<>(getWalletConnectV2SessionItems()); + sessionFetchCallback.onFetched(result); + } + private List getWalletConnectV2SessionItems() { List result = new ArrayList<>(); diff --git a/app/src/main/java/com/alphawallet/app/service/WalletConnectV2Service.java b/app/src/main/java/com/alphawallet/app/service/WalletConnectV2Service.java index f9741f7c71..10855adb14 100644 --- a/app/src/main/java/com/alphawallet/app/service/WalletConnectV2Service.java +++ b/app/src/main/java/com/alphawallet/app/service/WalletConnectV2Service.java @@ -5,7 +5,6 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; -import android.content.Context; import android.content.Intent; import android.os.Build; import android.os.IBinder; @@ -21,6 +20,9 @@ @AndroidEntryPoint public class WalletConnectV2Service extends Service { + private static final String TAG = WalletConnectV2Service.class.getName(); + + final String CHANNEL_ID = "WalletConnectV2Service"; @Override public IBinder onBind(Intent intent) { @@ -32,32 +34,38 @@ public IBinder onBind(Intent intent) public void onCreate() { super.onCreate(); - String CHANNEL_ID = "my_channel_01"; - NotificationChannel channel = new NotificationChannel(CHANNEL_ID, - "WalletConnect V2", - NotificationManager.IMPORTANCE_DEFAULT); + } - NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - notificationManager.createNotificationChannel(channel); + private Notification createNotification() + { + Intent notificationIntent = new Intent(this, WalletConnectNotificationActivity.class); + PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE); - Intent intent = new Intent(getApplicationContext(), WalletConnectNotificationActivity.class); - PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE); - Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID) - .setSmallIcon(R.drawable.ic_logo) - .setOnlyAlertOnce(true) + return new NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle(getString(R.string.notify_wallet_connect_title)) .setContentText(getString(R.string.notify_wallet_connect_content)) + .setSmallIcon(R.drawable.ic_logo) .setContentIntent(pendingIntent) .build(); - - startForeground(1, notification); - notificationManager.notify(1, notification); } - @Override - public int onStartCommand(Intent intent, int flags, int startId) + @RequiresApi(api = Build.VERSION_CODES.O) + private void createNotificationChannel() { - return super.onStartCommand(intent, flags, startId); + CharSequence name = getString(R.string.notify_wallet_connect_title); + String description = getString(R.string.notify_wallet_connect_content); + NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, NotificationManager.IMPORTANCE_DEFAULT); + channel.setDescription(description); + NotificationManager notificationManager = getSystemService(NotificationManager.class); + notificationManager.createNotificationChannel(channel); + } + @RequiresApi(api = Build.VERSION_CODES.O) + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + createNotificationChannel(); + Notification notification = createNotification(); + startForeground(1, notification); + return START_STICKY; } @Override diff --git a/app/src/main/java/com/alphawallet/app/ui/HomeActivity.java b/app/src/main/java/com/alphawallet/app/ui/HomeActivity.java index d4d65d1916..b4fb77ee1e 100644 --- a/app/src/main/java/com/alphawallet/app/ui/HomeActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/HomeActivity.java @@ -319,7 +319,7 @@ public void onPageScrollStateChanged(int state) if (data != null) { - checkIntents(data.toString(), intent); + handleDeeplink(data.toString(), intent); } Intent i = new Intent(this, PriceAlertsService.class); @@ -453,6 +453,13 @@ private void setupFragmentListeners() hideDialog(); qrCodeScanner.launch(options); }); + + getSupportFragmentManager() + .setFragmentResultListener(C.AWALLET_CODE, this, (requestKey, b) -> + { + String code = b.getString(C.AWALLET_CODE); + handleDeeplink(code, null); + }); } //TODO: Implement all QR scan using this method @@ -476,7 +483,7 @@ public void onNewIntent(Intent startIntent) if (data != null) { - checkIntents(data.toString(), startIntent); + handleDeeplink(data.toString(), startIntent); } } @@ -603,7 +610,7 @@ public void onSaveInstanceState(@NonNull Bundle savedInstanceState) } @Override - protected void onRestoreInstanceState(Bundle savedInstanceState) + protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); int oldPage = savedInstanceState.getInt(STORED_PAGE); @@ -1095,9 +1102,8 @@ private void showSystemUI() inset.show(WindowInsetsCompat.Type.statusBars() | WindowInsetsCompat.Type.navigationBars()); } - private void checkIntents(String importData, Intent startIntent) + private void handleDeeplink(String importData, Intent startIntent) { - //decode deeplink and handle DeepLinkRequest request = DeepLinkService.parseIntent(importData, startIntent); switch (request.type) { diff --git a/app/src/main/java/com/alphawallet/app/ui/WalletFragment.java b/app/src/main/java/com/alphawallet/app/ui/WalletFragment.java index 520238d1f3..ba45d3bf1a 100644 --- a/app/src/main/java/com/alphawallet/app/ui/WalletFragment.java +++ b/app/src/main/java/com/alphawallet/app/ui/WalletFragment.java @@ -118,6 +118,7 @@ public class WalletFragment extends BaseFragment implements private ActivityResultLauncher handleBackupClick; private ActivityResultLauncher tokenManagementLauncher; private boolean completed = false; + private boolean hasWCSession = false; @Inject AWWalletConnectClient awWalletConnectClient; @@ -270,6 +271,7 @@ private void initViewModel() viewModel.removeDisplayTokens().observe(getViewLifecycleOwner(), this::removeTokens); viewModel.getTokensService().startWalletSync(this); viewModel.activeWalletConnectSessions().observe(getViewLifecycleOwner(), walletConnectSessionItems -> { + hasWCSession = !walletConnectSessionItems.isEmpty(); adapter.showActiveWalletConnectSessions(walletConnectSessionItems); }); } @@ -828,7 +830,7 @@ public void onWCClicked() @Override public boolean hasWCSession() { - return awWalletConnectClient != null && awWalletConnectClient.hasWalletConnectSessions(); + return hasWCSession || (awWalletConnectClient != null && awWalletConnectClient.hasWalletConnectSessions()); } @Override diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/TokensAdapter.java b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/TokensAdapter.java index 391544f0a0..59771e20e3 100644 --- a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/TokensAdapter.java +++ b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/TokensAdapter.java @@ -623,14 +623,7 @@ public List getSelected() public void showActiveWalletConnectSessions(List sessions) { - if (sessions.isEmpty()) - { - removeItem(WalletConnectSessionHolder.VIEW_TYPE); - } - else - { - items.add(new WalletConnectSessionSortedItem(sessions, 2)); - } + checkWalletConnect(); } public void removeItem(int viewType) diff --git a/app/src/main/java/com/alphawallet/app/util/Utils.java b/app/src/main/java/com/alphawallet/app/util/Utils.java index 1a40ca3448..5a136bcdcb 100644 --- a/app/src/main/java/com/alphawallet/app/util/Utils.java +++ b/app/src/main/java/com/alphawallet/app/util/Utils.java @@ -73,7 +73,6 @@ import java.nio.channels.FileChannel; import java.nio.charset.StandardCharsets; import java.text.DateFormat; -import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -81,7 +80,6 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -115,7 +113,7 @@ public static int dp2px(Context context, int dp) public static String formatUrl(String url) { - if (URLUtil.isHttpsUrl(url) || URLUtil.isHttpUrl(url)) + if (URLUtil.isHttpsUrl(url) || URLUtil.isHttpUrl(url) || isWalletPrefix(url)) { return url; } @@ -132,6 +130,16 @@ public static String formatUrl(String url) } } + public static boolean isWalletPrefix(String url) + { + return url.startsWith(C.DAPP_PREFIX_TELEPHONE) || + url.startsWith(C.DAPP_PREFIX_MAILTO) || + url.startsWith(C.DAPP_PREFIX_ALPHAWALLET) || + url.startsWith(C.DAPP_PREFIX_MAPS) || + url.startsWith(C.DAPP_PREFIX_WALLETCONNECT) || + url.startsWith(C.DAPP_PREFIX_AWALLET); + } + public static boolean isValidUrl(String url) { if (TextUtils.isEmpty(url)) return false; diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/TokenFunctionViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/TokenFunctionViewModel.java index d4c877daa8..a0d357f6ee 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/TokenFunctionViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/TokenFunctionViewModel.java @@ -589,7 +589,7 @@ public void updateTokenScriptViewSize(Token token, int itemViewHeight) public void checkForNewScript(Token token) { if (token == null) return; - //check server for new tokenscript + //check server for new TokenScript scriptUpdate = assetDefinitionService.checkServerForScript(token, scriptUpdateInProgress) .observeOn(Schedulers.io()) .subscribeOn(Schedulers.single()) diff --git a/app/src/main/java/com/alphawallet/app/walletconnect/AWWalletConnectClient.java b/app/src/main/java/com/alphawallet/app/walletconnect/AWWalletConnectClient.java index e30689d8d3..ab8cc6d954 100644 --- a/app/src/main/java/com/alphawallet/app/walletconnect/AWWalletConnectClient.java +++ b/app/src/main/java/com/alphawallet/app/walletconnect/AWWalletConnectClient.java @@ -17,6 +17,7 @@ import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; import androidx.lifecycle.MutableLiveData; import com.alphawallet.app.App; @@ -83,7 +84,7 @@ public class AWWalletConnectClient implements Web3Wallet.WalletDelegate private ActionSheetCallback actionSheetCallback; private boolean hasConnection; private Application application; - private PreferenceRepositoryType preferenceRepository; + private final PreferenceRepositoryType preferenceRepository; public AWWalletConnectClient(Context context, WalletConnectInteract walletConnectInteract, PreferenceRepositoryType preferenceRepository, GasService gasService) { @@ -94,23 +95,9 @@ public AWWalletConnectClient(Context context, WalletConnectInteract walletConnec hasConnection = false; } - /*public void onSessionDelete(@NonNull Model.SessionDelete deletedSession) + public void onSessionDelete(@NonNull Model.SessionDelete deletedSession) { - updateNotification(); - }*/ - - public void onSessionProposal(@NonNull Model.SessionProposal sessionProposal) - { - WalletConnectV2SessionItem sessionItem = WalletConnectV2SessionItem.from(sessionProposal); - if (!validChainId(sessionItem.chains)) - { - return; - } - AWWalletConnectClient.sessionProposal = sessionProposal; - Intent intent = new Intent(context, WalletConnectV2Activity.class); - intent.putExtra("session", sessionItem); - intent.setFlags(FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); + updateNotification(null); } public boolean hasWalletConnectSessions() @@ -135,36 +122,6 @@ private boolean validChainId(List chains) return true; } - public void onSessionRequest(@NonNull Model.SessionRequest sessionRequest) - { - String checkMethod; - String method = sessionRequest.getRequest().getMethod(); - if (method.startsWith("eth_signTypedData")) - { - checkMethod = "eth_signTypedData"; - } - else - { - checkMethod = method; - } - - if (!WCMethodChecker.includes(checkMethod)) - { - reject(sessionRequest); - return; - } - - Model.Session settledSession = getSession(sessionRequest.getTopic()); - - Activity topActivity = App.getInstance().getTopActivity(); - if (topActivity != null) - { - WalletConnectV2SessionRequestHandler handler = new WalletConnectV2SessionRequestHandler(sessionRequest, settledSession, topActivity, this); - handler.handle(method, actionSheetCallback); - requestHandlers.append(sessionRequest.getRequest().getId(), handler); - } - } - private Session getSession(String topic) { List listOfSettledSessions; @@ -223,7 +180,7 @@ public void approve(Model.SessionProposal sessionProposal, List selected Params.SessionApprove approve = new Params.SessionApprove(proposerPublicKey, buildNamespaces(sessionProposal, selectedAccounts), sessionProposal.getRelayProtocol()); Web3Wallet.INSTANCE.approveSession(approve, sessionApprove -> { new Handler(Looper.getMainLooper()).postDelayed(() -> { - //updateNotification(); + updateNotification(sessionProposal); callback.onSessionProposalApproved(); }, 500); return null; @@ -295,18 +252,35 @@ public MutableLiveData> sessionItemMutableLiveDat return sessionItemMutableLiveData; } - private void updateService(Context context, List walletConnectSessionItems) + public void updateNotification(Model.SessionProposal sessionProposal) { - try - { - if (walletConnectSessionItems.isEmpty()) + walletConnectInteract.fetchSessions(items -> { + if (sessionProposal != null && items.isEmpty()) { - context.stopService(new Intent(context, WalletConnectV2Service.class)); + items.add(WalletConnectV2SessionItem.from(sessionProposal)); } - else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + + updateService(items); + sessionItemMutableLiveData.postValue(items); + }); + } + + private void updateService(List items) + { + try + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - Intent service = new Intent(context, WalletConnectV2Service.class); - context.startForegroundService(service); + if (items.isEmpty()) + { + context.stopService(new Intent(context, WalletConnectV2Service.class)); + //now signal + } + else + { + Intent serviceIntent = new Intent(context, WalletConnectV2Service.class); + ContextCompat.startForegroundService(context, serviceIntent); + } } } catch (Exception e) @@ -319,7 +293,6 @@ else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) public void reject(Model.SessionProposal sessionProposal, WalletConnectV2Callback callback) { - Web3Wallet.INSTANCE.rejectSession( new Params.SessionReject(sessionProposal.getProposerPublicKey(), context.getString(R.string.message_reject_request)), sessionReject -> null, @@ -363,8 +336,19 @@ public void setCallback(ActionSheetCallback actionSheetCallback) this.actionSheetCallback = actionSheetCallback; } + public String getRelayServer() + { + return String.format("%s/?projectId=%s", C.WALLET_CONNECT_REACT_APP_RELAY_URL, keyProvider.getWalletConnectProjectId()); + } + public void init(Application application) { + if (keyProvider.getWalletConnectProjectId().isEmpty()) + { + //Early return for no wallet connect + return; + } + this.application = application; Core.Model.AppMetaData appMetaData = getAppMetaData(application); String relayServer = String.format("%s/?projectId=%s", C.WALLET_CONNECT_REACT_APP_RELAY_URL, keyProvider.getWalletConnectProjectId()); @@ -386,6 +370,8 @@ public void init(Application application) try { Web3Wallet.INSTANCE.setWalletDelegate(this); + //ensure notification is displayed if session is active + updateNotification(null); } catch (Exception e) { @@ -394,7 +380,7 @@ public void init(Application application) } @NonNull - private Core.Model.AppMetaData getAppMetaData(Application application) + public Core.Model.AppMetaData getAppMetaData(Application application) { String name = application.getString(R.string.app_name); String url = C.ALPHAWALLET_WEBSITE; @@ -466,12 +452,6 @@ private void onSign(SignatureFromKey signatureFromKey, WalletConnectV2SessionReq } } - /*@Override - public void onAuthRequest(@NonNull Model.AuthRequest authRequest) - { - showApprovalDialog(authRequest); - }*/ - private void showApprovalDialog(Model.AuthRequest authRequest) { String activeWallet = preferenceRepository.getCurrentWalletAddress(); @@ -664,12 +644,6 @@ public void onSessionRequest(@NonNull Model.SessionRequest sessionRequest, @NonN } } - @Override - public void onSessionDelete(@NonNull Model.SessionDelete sessionDelete) - { - - } - public Intent getSessionIntent(Context appContext) { Intent intent; diff --git a/app/src/main/java/com/alphawallet/app/widget/TokenIcon.java b/app/src/main/java/com/alphawallet/app/widget/TokenIcon.java index c2752b29cd..837cd8e926 100644 --- a/app/src/main/java/com/alphawallet/app/widget/TokenIcon.java +++ b/app/src/main/java/com/alphawallet/app/widget/TokenIcon.java @@ -265,11 +265,6 @@ private void displayTokenIcon(String iconUrl) { setupDefaultIcon(); - if (token.tokenInfo.address.equalsIgnoreCase("0xfaafdc07907ff5120a76b34b731b278c38d6043c")) - { - System.out.println("YOLESS"); - } - final RequestOptions optionalCircleCrop = squareToken || iconUrl.startsWith(ALPHAWALLET_REPO_NAME) ? new RequestOptions() : new RequestOptions().circleCrop(); currentRq = Glide.with(this) From 0b15f6c575537abd6cfc201c2ef49fcd12ff879d Mon Sep 17 00:00:00 2001 From: James Brown Date: Mon, 25 Mar 2024 16:43:19 +1100 Subject: [PATCH 2/4] - Fix for Opensea service update --- .../main/java/com/alphawallet/app/service/OpenSeaService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/alphawallet/app/service/OpenSeaService.java b/app/src/main/java/com/alphawallet/app/service/OpenSeaService.java index 04792d2849..0593b13921 100644 --- a/app/src/main/java/com/alphawallet/app/service/OpenSeaService.java +++ b/app/src/main/java/com/alphawallet/app/service/OpenSeaService.java @@ -159,7 +159,7 @@ public Single getTokens(String address, //process this page of results processOpenseaTokens(foundTokens, assets, address, networkId, networkName, tokensService); currentPage++; - pageCursor = result.getString("next"); + pageCursor = result.has("next") ? result.getString("next") : ""; if (TextUtils.isEmpty(pageCursor)) { break; From fb3fa742836174d36fca30b01ce1a36581598ef4 Mon Sep 17 00:00:00 2001 From: James Brown Date: Mon, 25 Mar 2024 16:44:59 +1100 Subject: [PATCH 3/4] - fix dappbrowser URL intercept --- .../app/ui/DappBrowserFragment.java | 147 ++++++++++-------- .../com/alphawallet/app/web3/Web3View.java | 12 +- .../alphawallet/app/widget/AddressBar.java | 2 +- 3 files changed, 92 insertions(+), 69 deletions(-) diff --git a/app/src/main/java/com/alphawallet/app/ui/DappBrowserFragment.java b/app/src/main/java/com/alphawallet/app/ui/DappBrowserFragment.java index 030575160c..5a52b1fbd2 100644 --- a/app/src/main/java/com/alphawallet/app/ui/DappBrowserFragment.java +++ b/app/src/main/java/com/alphawallet/app/ui/DappBrowserFragment.java @@ -38,6 +38,7 @@ import android.webkit.WebBackForwardList; import android.webkit.WebChromeClient; import android.webkit.WebHistoryItem; +import android.webkit.WebResourceRequest; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.FrameLayout; @@ -72,7 +73,6 @@ import com.alphawallet.app.entity.TransactionReturn; import com.alphawallet.app.entity.URLLoadInterface; import com.alphawallet.app.entity.Wallet; -import com.alphawallet.app.entity.WalletConnectActions; import com.alphawallet.app.entity.WalletType; import com.alphawallet.app.entity.analytics.ActionSheetSource; import com.alphawallet.app.entity.analytics.QrScanResultType; @@ -218,7 +218,7 @@ public void onActivityResult(Uri uri) // Need to handle the inverse event where the keyboard is hidden, and we size the page back // (Remembering to allow for the navigation bar). private final View.OnApplyWindowInsetsListener resizeListener = (v, insets) -> { - if (v == null || getActivity() == null) + if (getActivity() == null) { return insets; } @@ -896,66 +896,11 @@ public boolean onShowFileChooser(WebView webView, ValueCallback filePathC web3.setWebViewClient(new WebViewClient() { @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) + public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { - String[] prefixCheck = url.split(":"); - if (prefixCheck.length > 1) - { - Intent intent; - switch (prefixCheck[0]) - { - case C.DAPP_PREFIX_TELEPHONE: - intent = new Intent(Intent.ACTION_DIAL); - intent.setData(Uri.parse(url)); - startActivity(Intent.createChooser(intent, "Call " + prefixCheck[1])); - return true; - case C.DAPP_PREFIX_MAILTO: - intent = new Intent(Intent.ACTION_SENDTO); - intent.setData(Uri.parse(url)); - startActivity(Intent.createChooser(intent, "Email: " + prefixCheck[1])); - return true; - case C.DAPP_PREFIX_ALPHAWALLET: - if (prefixCheck[1].equals(C.DAPP_SUFFIX_RECEIVE)) - { - viewModel.showMyAddress(getContext()); - return true; - } - break; - case C.DAPP_PREFIX_WALLETCONNECT: - //start walletconnect - if (wallet.type == WalletType.WATCH) - { - showWalletWatch(); - } - else - { - walletConnectSession = url; - if (getContext() != null) - viewModel.handleWalletConnect(getContext(), url, activeNetwork); - } - return true; - default: - break; - } - } - - if (fromWalletConnectModal(url)) - { - String encodedURL = url.split("=")[1]; - try - { - String decodedURL = URLDecoder.decode(encodedURL, Charset.defaultCharset().name()); - viewModel.handleWalletConnect(getContext(), decodedURL, activeNetwork); - return true; - } - catch (UnsupportedEncodingException e) - { - Timber.d("Decode URL failed: " + e); - } - } - - setUrlText(url); - return false; + final Uri uri = request.getUrl(); + final String url = uri.toString(); + return handlePrefix(url); } }); @@ -978,6 +923,77 @@ public boolean shouldOverrideUrlLoading(WebView view, String url) } } + private boolean handlePrefix(String url) + { + String[] prefixCheck = url.split(":"); + if (prefixCheck.length > 1) + { + Intent intent; + switch (prefixCheck[0]) + { + case C.DAPP_PREFIX_TELEPHONE: + intent = new Intent(Intent.ACTION_DIAL); + intent.setData(Uri.parse(url)); + startActivity(Intent.createChooser(intent, "Call " + prefixCheck[1])); + return true; + case C.DAPP_PREFIX_MAILTO: + intent = new Intent(Intent.ACTION_SENDTO); + intent.setData(Uri.parse(url)); + startActivity(Intent.createChooser(intent, "Email: " + prefixCheck[1])); + return true; + case C.DAPP_PREFIX_ALPHAWALLET: + if (prefixCheck[1].equals(C.DAPP_SUFFIX_RECEIVE)) + { + viewModel.showMyAddress(getContext()); + return true; + } + break; + case C.DAPP_PREFIX_AWALLET: + handleAWCode(url); + return true; + case C.DAPP_PREFIX_WALLETCONNECT: + //start walletconnect + if (wallet.type == WalletType.WATCH) + { + showWalletWatch(); + } + else + { + walletConnectSession = url; + if (getContext() != null) + viewModel.handleWalletConnect(getContext(), url, activeNetwork); + } + return true; + default: + break; + } + } + + if (fromWalletConnectModal(url)) + { + String encodedURL = url.split("=")[1]; + try + { + String decodedURL = URLDecoder.decode(encodedURL, Charset.defaultCharset().name()); + viewModel.handleWalletConnect(getContext(), decodedURL, activeNetwork); + return true; + } + catch (UnsupportedEncodingException e) + { + Timber.d("Decode URL failed: %s", e.getMessage()); + } + } + + return false; + } + + private void handleAWCode(String awCode) + { + Bundle codeBundle = new Bundle(); + codeBundle.putString(C.AWALLET_CODE, awCode); + getParentFragmentManager().setFragmentResult(C.AWALLET_CODE, codeBundle); + } + private boolean fromWalletConnectModal(String url) { return url.startsWith("https://" + mainnetMagicLinkDomain + "/wc?uri="); @@ -1477,7 +1493,14 @@ private boolean loadUrl(String urlText) detachFragments(); addToBackStack(DAPP_BROWSER); cancelSearchSession(); - if (checkForMagicLink(urlText)) return true; + if (checkForMagicLink(urlText)) + { + return true; + } + else if (handlePrefix(urlText)) + { + return true; + } web3.resetView(); web3.loadUrl(Utils.formatUrl(urlText)); setUrlText(Utils.formatUrl(urlText)); diff --git a/app/src/main/java/com/alphawallet/app/web3/Web3View.java b/app/src/main/java/com/alphawallet/app/web3/Web3View.java index 58ca2c668e..f2f6383435 100644 --- a/app/src/main/java/com/alphawallet/app/web3/Web3View.java +++ b/app/src/main/java/com/alphawallet/app/web3/Web3View.java @@ -3,7 +3,7 @@ import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; -import android.os.Build; +import android.net.Uri; import android.util.AttributeSet; import android.webkit.WebChromeClient; import android.webkit.WebResourceError; @@ -14,7 +14,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; import androidx.webkit.WebSettingsCompat; import androidx.webkit.WebViewFeature; @@ -141,7 +140,7 @@ public void setWebChromeClient(WebChromeClient client) } @Override - public void setWebViewClient(WebViewClient client) + public void setWebViewClient(@NonNull WebViewClient client) { super.setWebViewClient(new WrapWebViewClient(webViewClient, client)); } @@ -366,11 +365,12 @@ else if (!loadingError && loadInterface != null) } @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) - { + public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { + final Uri uri = request.getUrl(); + final String url = uri.toString(); redirect = true; - return externalClient.shouldOverrideUrlLoading(view, url) + return externalClient.shouldOverrideUrlLoading(view, request) || internalClient.shouldOverrideUrlLoading(view, url); } diff --git a/app/src/main/java/com/alphawallet/app/widget/AddressBar.java b/app/src/main/java/com/alphawallet/app/widget/AddressBar.java index 7c24d2cadb..ee93999f83 100644 --- a/app/src/main/java/com/alphawallet/app/widget/AddressBar.java +++ b/app/src/main/java/com/alphawallet/app/widget/AddressBar.java @@ -47,7 +47,7 @@ public class AddressBar extends MaterialToolbar implements ItemClickListener private ImageView home; @Nullable - private Disposable disposable; + private Disposable disposable; // awallet://openURL?https%3A%2F%2Fsmart-layer.vercel.app private boolean focused; public AddressBar(Context context, AttributeSet attributeSet) From ca79490472243ac8bbf7dd3ad8b77f8325f6af3a Mon Sep 17 00:00:00 2001 From: James Brown Date: Mon, 25 Mar 2024 16:49:42 +1100 Subject: [PATCH 4/4] Add ability to use STL-Launchpad URI --- .../app/service/AssetDefinitionService.java | 74 +++++++++++++------ .../app/ui/GasSettingsActivity.java | 2 +- .../app/widget/ActionSheetDialog.java | 2 +- .../token/tools/TokenDefinition.java | 3 + 4 files changed, 58 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/alphawallet/app/service/AssetDefinitionService.java b/app/src/main/java/com/alphawallet/app/service/AssetDefinitionService.java index 56e19b4aa2..33d73bb989 100644 --- a/app/src/main/java/com/alphawallet/app/service/AssetDefinitionService.java +++ b/app/src/main/java/com/alphawallet/app/service/AssetDefinitionService.java @@ -2,8 +2,12 @@ import static com.alphawallet.app.repository.TokenRepository.getWeb3jService; import static com.alphawallet.app.repository.TokensRealmSource.IMAGES_DB; +import static com.alphawallet.ethereum.EthereumNetworkBase.MAINNET_ID; +import static com.alphawallet.token.tools.TokenDefinition.TOKENSCRIPT_ADDRESS; +import static com.alphawallet.token.tools.TokenDefinition.TOKENSCRIPT_CHAIN; import static com.alphawallet.token.tools.TokenDefinition.TOKENSCRIPT_CURRENT_SCHEMA; import static com.alphawallet.token.tools.TokenDefinition.TOKENSCRIPT_REPO_SERVER; +import static com.alphawallet.token.tools.TokenDefinition.TOKENSCRIPT_STORE_SERVER; import static com.alphawallet.token.tools.TokenDefinition.UNCHANGED_SCRIPT; import android.Manifest; @@ -67,6 +71,8 @@ import com.alphawallet.token.tools.TokenDefinition; import org.jetbrains.annotations.NotNull; +import org.json.JSONArray; +import org.json.JSONObject; import org.web3j.abi.DefaultFunctionEncoder; import org.web3j.abi.FunctionEncoder; import org.web3j.abi.datatypes.Function; @@ -385,9 +391,10 @@ private void handleFileLoadError(Throwable throwable, File file) private TokenDefinition fileLoadComplete(List originContracts, TokenScriptFile file, TokenDefinition td) { - if (originContracts.size() == 0 || td.getAttestation() != null) return td; //no action needed, not accessible to Attestation //TODO: Refactor once we handle multiple attestations + if (originContracts.isEmpty() || td.getAttestation() != null) return td; //no action needed, not accessible to Attestation //TODO: Refactor once we handle multiple attestations boolean hasEvents = td.hasEvents(); + long primaryChainId = !originContracts.isEmpty() ? originContracts.iterator().next().chainId : MAINNET_ID; try (Realm realm = realmManager.getRealmInstance(ASSET_DEFINITION_DB)) { @@ -396,7 +403,7 @@ private TokenDefinition fileLoadComplete(List originContracts, { //delete this file and check downloads for update removeFile(file.getAbsolutePath()); - loadScriptFromServer(getFileName(file)); + loadScriptFromServer(primaryChainId, getFileName(file)); return td; } @@ -1011,7 +1018,7 @@ private TokenScriptFile locateTokenScriptFile(String fileName) * Get asset definition given contract address * * @param address - * @return + * @return The Token Definition for the given chain, address */ public TokenDefinition getAssetDefinition(long chainId, String address) { @@ -1026,7 +1033,7 @@ public TokenDefinition getAssetDefinition(long chainId, String address) if (assetDef == null && !address.equals("ethereum")) { //try web - loadScriptFromServer(address.toLowerCase()); //this will complete asynchronously and display will be updated + loadScriptFromServer(chainId, address.toLowerCase()); //this will complete asynchronously and display will be updated } return assetDef; // if nothing found use default @@ -1066,10 +1073,10 @@ public Single getAssetDefinitionASync(long chainId, String addr } String convertedAddr = (address.equalsIgnoreCase(tokensService.getCurrentAddress())) ? "ethereum" : address.toLowerCase(); - return getAssetDefinitionASync(getDefinition(getTSDataKey(chainId, address)), convertedAddr); + return getAssetDefinitionASync(getDefinition(getTSDataKey(chainId, address)), chainId, convertedAddr); } - private Single getAssetDefinitionASync(final TokenDefinition assetDef, final String contractName) + private Single getAssetDefinitionASync(final TokenDefinition assetDef, final long chainId, final String contractName) { if (assetDef != null) { @@ -1078,7 +1085,7 @@ private Single getAssetDefinitionASync(final TokenDefinition as else if (!contractName.equals("ethereum")) { //at this stage, this script isn't replacing any existing script, so it's safe to write to database without checking if we need to delete anything - return fetchXMLFromServer(contractName.toLowerCase()) + return fetchXMLFromServer(chainId, contractName.toLowerCase()) .flatMap(this::handleNewTSFile); } else return Single.fromCallable(TokenDefinition::new); @@ -1095,7 +1102,7 @@ public Single getAssetDefinitionASync(Token token) // hold until asset definitions have finished loading waitForAssets(); - return getAssetDefinitionASync(getDefinition(token.getTSKey()), contractName); + return getAssetDefinitionASync(getDefinition(token.getTSKey()), token.tokenInfo.chainId, contractName); } private void waitForAssets() @@ -1169,12 +1176,12 @@ public String getIssuerName(Token token) return issuer; } - private void loadScriptFromServer(String correctedAddress) + private void loadScriptFromServer(long chainId, String correctedAddress) { //first check the last time we tried this session if (assetChecked.get(correctedAddress) == null || (System.currentTimeMillis() > (assetChecked.get(correctedAddress) + 1000L * 60L * 60L))) { - fetchXMLFromServer(correctedAddress) + fetchXMLFromServer(chainId, correctedAddress) .flatMap(this::handleNewTSFile) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) @@ -1416,7 +1423,7 @@ private boolean matchesExistingScript(Token token, String uri) return false; } - private Single tryServerIfRequired(File contractScript, String address) + private Single tryServerIfRequired(File contractScript, String address, long chainId) { if (contractScript.exists() || contractScript.getName().equals(UNCHANGED_SCRIPT)) { @@ -1424,15 +1431,15 @@ private Single tryServerIfRequired(File contractScript, String address) } else { - return fetchXMLFromServer(address); + return fetchXMLFromServer(chainId, address); } } - private Single fetchXMLFromServer(String address) + private Single fetchXMLFromServer(long chainId, String address) { return Single.fromCallable(() -> { final File defaultReturn = new File(""); - if (address.equals("")) return defaultReturn; + if (address.isEmpty()) return defaultReturn; File result = getDownloadedXMLFile(address); @@ -1459,15 +1466,19 @@ private Single fetchXMLFromServer(String address) if (assetChecked.get(address) != null && (System.currentTimeMillis() > (assetChecked.get(address) + 1000L * 60L * 60L))) return result; - String sb = TOKENSCRIPT_REPO_SERVER + - TOKENSCRIPT_CURRENT_SCHEMA + - "/" + - address; + //use the updated server + String sb = TOKENSCRIPT_STORE_SERVER.replace(TOKENSCRIPT_ADDRESS, address).replace(TOKENSCRIPT_CHAIN, Long.toString(chainId)); Pair downloadResponse = downloadScript(sb, fileTime); - if (!TextUtils.isEmpty(downloadResponse.first)) + String offchainScriptUri = getOffchainScriptUri(downloadResponse); + + if (!TextUtils.isEmpty(offchainScriptUri)) { - result = storeFile(address, downloadResponse); + downloadResponse = downloadScript(offchainScriptUri, fileTime); + if (!TextUtils.isEmpty(downloadResponse.first)) + { + storeFile(address, downloadResponse); + } } assetChecked.put(address, System.currentTimeMillis()); @@ -1476,6 +1487,27 @@ private Single fetchXMLFromServer(String address) }); } + private String getOffchainScriptUri(Pair downloadResponse) + { + String offchainScriptResponse = ""; + try + { + JSONObject response = new JSONObject(downloadResponse.first); + JSONObject scriptUri = response.getJSONObject("scriptURI"); + JSONArray offchainLinks = scriptUri.getJSONArray("offchain"); + if (offchainLinks.length() > 0) + { + offchainScriptResponse = offchainLinks.getString(0); + } + } + catch (Exception e) + { + offchainScriptResponse = ""; + } + + return offchainScriptResponse; + } + private Pair downloadScript(String Uri, long currentFileTime) { if (Uri.equals(UNCHANGED_SCRIPT)) @@ -3116,7 +3148,7 @@ public Single checkServerForScript(Token token, MutableLiveData //try the contractURI, then server return fetchTokenScriptFromContract(token, updateFlag) - .flatMap(file -> tryServerIfRequired(file, token.getAddress().toLowerCase())) + .flatMap(file -> tryServerIfRequired(file, token.getAddress().toLowerCase(), token.tokenInfo.chainId)) .flatMap(this::handleNewTSFile) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); diff --git a/app/src/main/java/com/alphawallet/app/ui/GasSettingsActivity.java b/app/src/main/java/com/alphawallet/app/ui/GasSettingsActivity.java index 4600ac150b..f438da9de9 100644 --- a/app/src/main/java/com/alphawallet/app/ui/GasSettingsActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/GasSettingsActivity.java @@ -126,7 +126,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) gasSliderView.setNonce(getIntent().getLongExtra(C.EXTRA_NONCE, -1)); gasSliderView.initGasLimit(customGasLimit.toBigInteger(), presetGasLimit.toBigInteger()); gasSpread = getIntent().getParcelableExtra(C.EXTRA_GAS_PRICE); - isUsing1559 = getIntent().getBooleanExtra(C.EXTRA_1559_TX, false); + isUsing1559 = getIntent().getBooleanExtra(C.EXTRA_1559_TX, true); gasSliderView.initGasPrice(gasSpread.getSelectedGasFee(TXSpeed.CUSTOM)); adapter = new CustomAdapter(this); diff --git a/app/src/main/java/com/alphawallet/app/widget/ActionSheetDialog.java b/app/src/main/java/com/alphawallet/app/widget/ActionSheetDialog.java index 8a9e546fa6..61790836a2 100644 --- a/app/src/main/java/com/alphawallet/app/widget/ActionSheetDialog.java +++ b/app/src/main/java/com/alphawallet/app/widget/ActionSheetDialog.java @@ -327,7 +327,7 @@ public ActionSheetDialog(Activity activity, ActionSheetMode mode) private GasWidgetInterface setupGasWidget() { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); - boolean canUse1559Transactions = prefs.getBoolean(SharedPreferenceRepository.EXPERIMENTAL_1559_TX, false); + boolean canUse1559Transactions = prefs.getBoolean(SharedPreferenceRepository.EXPERIMENTAL_1559_TX, true); use1559Transactions = canUse1559Transactions && has1559Gas() //1559 Transactions toggled on in settings and this chain supports 1559 && !(token.isEthereum() && candidateTransaction.leafPosition == -2) //User not sweeping wallet (if so we need to use legacy tx) diff --git a/lib/src/main/java/com/alphawallet/token/tools/TokenDefinition.java b/lib/src/main/java/com/alphawallet/token/tools/TokenDefinition.java index 8fda51a6cd..1729093ce2 100644 --- a/lib/src/main/java/com/alphawallet/token/tools/TokenDefinition.java +++ b/lib/src/main/java/com/alphawallet/token/tools/TokenDefinition.java @@ -80,7 +80,10 @@ public class TokenDefinition public static final String TOKENSCRIPT_MINIMUM_SCHEMA = "2020/06"; public static final String TOKENSCRIPT_CURRENT_SCHEMA = "2022/09"; + public static final String TOKENSCRIPT_ADDRESS = "{TS_ADDRESS}"; + public static final String TOKENSCRIPT_CHAIN = "{TS_CHAIN}"; public static final String TOKENSCRIPT_REPO_SERVER = "https://repo.tokenscript.org/"; + public static final String TOKENSCRIPT_STORE_SERVER = "https://store-backend.smartlayer.network/tokenscript/" + TOKENSCRIPT_ADDRESS + "/chain/" + TOKENSCRIPT_CHAIN + "/script-uri"; public static final String TOKENSCRIPT_NAMESPACE = "http://tokenscript.org/" + TOKENSCRIPT_CURRENT_SCHEMA + "/tokenscript"; private static final String ATTESTATION = "http://attestation.id/ns/tbml";