diff --git a/app/build.gradle b/app/build.gradle index 66c7047..f38574f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -26,20 +26,6 @@ android { dependencies { implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'androidx.browser:browser:1.7.0' - implementation 'org.mozilla.geckoview:geckoview:143.0.20250929153833' - // Optional: If you plan to integrate platform FIDO2 / WebAuthn (Passkeys) via - // Google Play Services, add the FIDO dependency here. The line below adds a - // recent stable version — CI must have Google Play services available. + // FIDO2 dependency for WebView WebAuthn/Passkey support implementation 'com.google.android.gms:play-services-fido:20.1.0' - // Notes: - // - Choose a specific stable VERSION compatible with your compileSdk and Gradle plugin. - // - Adding this dependency requires the Android SDK + Google Play services available in CI/local. - // - Alternatively, some projects use platform WebAuthn or Gecko's internal WebAuthn bridge; prefer - // the approach that matches your Geckoview version and target devices. - // Notes: - // - Choose a specific stable VERSION compatible with your compileSdk and Gradle plugin. - // - Adding this dependency requires the Android SDK + Google Play services available in CI/local. - // - Alternatively, some projects use platform WebAuthn or Gecko's internal WebAuthn bridge; prefer - // the approach that matches your Geckoview version and target devices. } \ No newline at end of file diff --git a/app/src/main/java/com/example/simplebrowser/ChromeTabActivity.java b/app/src/main/java/com/example/simplebrowser/ChromeTabActivity.java deleted file mode 100644 index 38c1f4b..0000000 --- a/app/src/main/java/com/example/simplebrowser/ChromeTabActivity.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.example.simplebrowser; - -import android.net.Uri; -import android.os.Bundle; -import androidx.appcompat.app.AppCompatActivity; -import androidx.browser.customtabs.CustomTabsIntent; -import android.graphics.Color; - -/** - * 使用Chrome Custom Tabs打开网页 - * 完全支持Cloudflare验证和WebAuthn/通行密钥 - */ -public class ChromeTabActivity extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - String url = getIntent().getStringExtra("url"); - - // 如果从Intent data URI中获取URL - if (getIntent().getData() != null) { - android.net.Uri data = getIntent().getData(); - String uriString = data.toString(); - // 移除可能的fragment标记 - url = uriString.split("#")[0]; - } - - if (url != null && !url.isEmpty()) { - openUrlInCustomTab(url); - } - - // Chrome Custom Tabs会在外部打开,关闭此Activity - finish(); - } - - /** - * 使用Chrome Custom Tabs打开URL - * 优势: - * - 完全支持Cloudflare验证 - * - 完全支持WebAuthn/通行密钥 - * - 使用真实Chrome浏览器内核 - * - 共享Chrome的Cookie和登录状态 - * - 更好的性能和兼容性 - */ - private void openUrlInCustomTab(String url) { - CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(); - - // 自定义工具栏颜色 - builder.setToolbarColor(Color.parseColor("#2196F3")); - - // 显示网页标题 - builder.setShowTitle(true); - - // 启用URL栏隐藏(向下滚动时) - builder.setUrlBarHidingEnabled(true); - - // 添加默认分享按钮 - builder.setShareState(CustomTabsIntent.SHARE_STATE_ON); - - // 设置启动动画 - builder.setStartAnimations(this, android.R.anim.slide_in_left, android.R.anim.slide_out_right); - builder.setExitAnimations(this, android.R.anim.slide_in_left, android.R.anim.slide_out_right); - - CustomTabsIntent customTabsIntent = builder.build(); - - // 启动Chrome Custom Tab - customTabsIntent.launchUrl(this, Uri.parse(url)); - } -} diff --git a/app/src/main/java/com/example/simplebrowser/GeckoViewActivity.java b/app/src/main/java/com/example/simplebrowser/GeckoViewActivity.java deleted file mode 100644 index 5e74500..0000000 --- a/app/src/main/java/com/example/simplebrowser/GeckoViewActivity.java +++ /dev/null @@ -1,403 +0,0 @@ -package com.example.simplebrowser; - -import android.os.Bundle; -import android.view.View; -import android.view.KeyEvent; -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.IntentSenderRequest; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import org.mozilla.geckoview.GeckoRuntime; -import org.mozilla.geckoview.GeckoSession; -import org.mozilla.geckoview.GeckoView; -import android.util.Base64; -import android.util.Log; -import java.util.Collections; -import com.google.android.gms.fido.Fido; -import android.app.DownloadManager; -import android.os.Environment; -import android.webkit.URLUtil; -import android.app.AlertDialog; -import android.content.ClipData; -import android.content.ClipboardManager; -import android.widget.Toast; -import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialCreationOptions; -import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialRpEntity; -import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialParameters; -import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialUserEntity; -import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialRequestOptions; -import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialType; -import com.google.android.gms.tasks.OnFailureListener; -import com.google.android.gms.tasks.OnSuccessListener; -// Note: avoid direct compile-time dependency on Fido2PendingIntent type to improve -// compatibility across Play Services versions — we'll handle the returned object -// at runtime (instanceof or reflection) to extract an IntentSender. -import android.app.PendingIntent; -import androidx.activity.result.IntentSenderRequest; - -/** - * 使用GeckoView(Firefox引擎)打开网页 - * 完全支持全屏、WebAuthn和现代Web标准 - */ -public class GeckoViewActivity extends AppCompatActivity { - - private GeckoView geckoView; - private GeckoSession geckoSession; - private static GeckoRuntime sRuntime; - // 是否使用桌面模式(强制桌面 UA + 注入脚本) - private static final boolean FORCE_DESKTOP = true; - // ActivityResult launcher placeholder for FIDO2 / WebAuthn IntentSender - private ActivityResultLauncher fido2Launcher; - private static final String TAG = "GeckoViewActivity"; - private volatile boolean mCanGoBack = false; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // 创建GeckoView - geckoView = new GeckoView(this); - - // 初始化GeckoRuntime(整个应用只需一个实例) - if (sRuntime == null) { - sRuntime = GeckoRuntime.create(this); - } - - - // 创建并配置GeckoSession - geckoSession = new GeckoSession(); - // 如果需要设置 UA,GeckoView/GeckoSession 的 API 在不同版本中差异较大。 - // 为保证兼容性,本实现改为通过注入脚本强制桌面特征(见后续注入逻辑)。 - geckoSession.open(sRuntime); - geckoView.setSession(geckoSession); - - // 内容委托:下载与长按上下文菜单 - geckoSession.setContentDelegate(new GeckoSession.ContentDelegate() { - // 外部下载响应API当前Gecko版本不可用,使用长按“下载链接”手动触发。 - @Override - public void onContextMenu(GeckoSession session, int screenX, int screenY, - GeckoSession.ContentDelegate.ContextElement element) { - if (element == null) return; - java.util.ArrayList options = new java.util.ArrayList<>(); - final String link = element.linkUri; - final String src = element.srcUri; - if (link != null && !link.isEmpty()) { options.add("复制链接"); options.add("下载链接"); } - if (src != null && !src.isEmpty()) options.add("保存图片"); - if (options.isEmpty()) return; - new AlertDialog.Builder(GeckoViewActivity.this) - .setTitle("操作") - .setItems(options.toArray(new String[0]), (dialog, which) -> { - String choice = options.get(which); - if ("复制链接".equals(choice) && link != null) { - try { - ClipboardManager cb = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); - cb.setPrimaryClip(ClipData.newPlainText("link", link)); - Toast.makeText(GeckoViewActivity.this, "已复制", Toast.LENGTH_SHORT).show(); - } catch (Exception e) { Toast.makeText(GeckoViewActivity.this, "复制失败", Toast.LENGTH_SHORT).show(); } - } else if ("下载链接".equals(choice) && link != null) { - try { - DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE); - android.net.Uri uriL = android.net.Uri.parse(link); - DownloadManager.Request reqL = new DownloadManager.Request(uriL); - String fileNameL = URLUtil.guessFileName(link, null, null); - reqL.setTitle(fileNameL); - reqL.setDescription("正在下载"); - reqL.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); - reqL.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileNameL); - long idL = dm.enqueue(reqL); - startActivity(new android.content.Intent(GeckoViewActivity.this, DownloadActivity.class).putExtra("downloadId", idL)); - } catch (Exception e) { Toast.makeText(GeckoViewActivity.this, "下载失败", Toast.LENGTH_SHORT).show(); } - } else if ("保存图片".equals(choice) && src != null) { - try { - DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE); - android.net.Uri uri2 = android.net.Uri.parse(src); - DownloadManager.Request req2 = new DownloadManager.Request(uri2); - String imgName = URLUtil.guessFileName(src, null, null); - req2.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); - req2.setDestinationInExternalPublicDir(Environment.DIRECTORY_PICTURES, imgName); - long id2 = dm.enqueue(req2); - startActivity(new android.content.Intent(GeckoViewActivity.this, DownloadActivity.class).putExtra("downloadId", id2)); - } catch (Exception e) { Toast.makeText(GeckoViewActivity.this, "保存失败", Toast.LENGTH_SHORT).show(); } - } - }) - .show(); - } - }); - - // 监听导航能力变化,更新是否可后退 - geckoSession.setNavigationDelegate(new GeckoSession.NavigationDelegate() { - @Override - public void onCanGoBack(GeckoSession session, boolean canGoBack) { - mCanGoBack = canGoBack; - } - @Override - public void onCanGoForward(GeckoSession session, boolean canGoForward) { /* no-op */ } - }); - - // 初始化 FIDO2 / WebAuthn 的 ActivityResultLauncher(使用 IntentSender) - fido2Launcher = registerForActivityResult( - new ActivityResultContracts.StartIntentSenderForResult(), - result -> { - // 这里处理 FIDO2 IntentSender 返回的数据 - if (result != null && result.getResultCode() == RESULT_OK && result.getData() != null) { - // TODO: 将 result.getData() 中的 attestation/assertion bytes 提取并发送回 Gecko(或通过 Geckoview API 回填) - android.util.Log.d("GeckoViewActivity", "FIDO2 result OK: " + result.getData()); - } else { - android.util.Log.d("GeckoViewActivity", "FIDO2 result canceled or null"); - } - } - ); - // 获取URL - String url = getIntent().getStringExtra("url"); - if (getIntent().getData() != null) { - android.net.Uri data = getIntent().getData(); - String uriString = data.toString(); - url = uriString.split("#")[0]; - } - - // 加载URL - if (url != null && !url.isEmpty()) { - geckoSession.loadUri(url); - } - - // 如果强制桌面,注入类似 WebViewActivity 的脚本以覆盖 navigator/screen 等属性 - if (FORCE_DESKTOP) { - final String desktopSpoofScript = getDesktopSpoofScript(); - // 在页面加载后注入脚本 - 使用简单的监听器(GeckoSession.loadUri 不直接提供 onPageFinished 的钩子) - // 所以我们使用 evaluateJS 在短延迟后注入,作为兼容实现 - // GeckoView 是一个 View,直接设置 layerType - geckoView.setLayerType(View.LAYER_TYPE_HARDWARE, null); - geckoView.postDelayed(new Runnable() { - @Override - public void run() { - try { - java.lang.reflect.Method eval = geckoSession.getClass().getMethod("evaluateJS", String.class); - eval.invoke(geckoSession, desktopSpoofScript); - } catch (NoSuchMethodException nsme) { - // evaluateJS 不存在则跳过,避免影响历史 - } catch (Throwable ignored) {} - } - }, 500); - } - - setContentView(geckoView); - - // 全屏设置 - getWindow().getDecorView().setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE | - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | - View.SYSTEM_UI_FLAG_FULLSCREEN | - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - ); - - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) { - getWindow().setDecorFitsSystemWindows(false); - } - - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) { - android.view.WindowManager.LayoutParams lp = getWindow().getAttributes(); - lp.layoutInDisplayCutoutMode = - android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; - getWindow().setAttributes(lp); - } - } - - /** - * 占位:当 Gecko 页面触发 WebAuthn 时,请调用此方法以启动本地 FIDO2 流程。 - * 实际实现建议: - * 1) 在 build.gradle 中添加 Google Play Services FIDO2 依赖(示例:com.google.android.gms:play-services-fido:) - * 2) 使用 Fido.getFido2ApiClient(this).getRegisterIntent(...)/getSignIntent(...) 获取 PendingIntent 或 Intent - * 3) 使用 fido2Launcher.launch(intent) 启动,并在 launcher 回调中处理结果,将 attestation/assertion bytes 传回给页面或 Gecko - * 注意:某些 geckoview 版本提供了直接的 WebAuthn delegate 回调,优先使用 Geckoview 提供的 API将结果直接回填给引擎。 - */ - private void startFido2FlowPlaceholder() { - // 占位:当前环境下不启动任何 IntentSender(避免类型不匹配); - // 真实环境可调用 startFido2Register/startFido2Sign - android.util.Log.w(TAG, "startFido2FlowPlaceholder: FIDO2 launcher not invoked in placeholder mode"); - } - - /** - * 使用 Play Services FIDO2 发起注册(Create)流程。 - * 参数说明: - * - challengeB64: 来自服务端的 challenge(Base64 编码) - * - rpId: relying party id(通常为域名) - * - rpName: 显示名 - * - userIdB64: 用户 id(Base64 编码) - * - userName: 用户名 - * 注意:页面应将上述参数通过消息桥(或其他方式)传递给原生层。 - */ - public void startFido2Register(String challengeB64, String rpId, String rpName, String userIdB64, String userName) { - try { - byte[] challenge = Base64.decode(challengeB64, Base64.DEFAULT); - byte[] userId = Base64.decode(userIdB64, Base64.DEFAULT); - - PublicKeyCredentialRpEntity rp = new PublicKeyCredentialRpEntity(rpId, rpName, null); - PublicKeyCredentialUserEntity user = new PublicKeyCredentialUserEntity(userId, userName, userName, null); - PublicKeyCredentialParameters params = new PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY.toString(), -7); // ES256 - - PublicKeyCredentialCreationOptions options = new PublicKeyCredentialCreationOptions.Builder() - .setRp(rp) - .setUser(user) - .setChallenge(challenge) - .setParameters(Collections.singletonList(params)) - .build(); - - Fido.getFido2ApiClient(this) - .getRegisterIntent(options) - .addOnSuccessListener(new OnSuccessListener() { - @Override - public void onSuccess(Object pending) { - try { - android.content.IntentSender sender = null; - if (pending == null) { - Log.w(TAG, "FIDO2 pending is null"); - } else if (pending instanceof PendingIntent) { - sender = ((PendingIntent) pending).getIntentSender(); - } else { - // Try reflection: some Play Services versions return Fido2PendingIntent - try { - java.lang.reflect.Method m = pending.getClass().getMethod("getIntentSender"); - Object o = m.invoke(pending); - if (o instanceof android.content.IntentSender) { - sender = (android.content.IntentSender) o; - } - } catch (NoSuchMethodException nsme) { - Log.w(TAG, "pending object has no getIntentSender method"); - } - } - if (sender != null) { - IntentSenderRequest req = new IntentSenderRequest.Builder(sender).build(); - fido2Launcher.launch(req); - } else { - Log.w(TAG, "FIDO2 pendingIntent has no IntentSender"); - } - } catch (Exception e) { - Log.e(TAG, "Failed to launch FIDO2 pending intent", e); - } - } - }) - .addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(Exception e) { - Log.e(TAG, "getRegisterIntent failed", e); - } - }); - - } catch (Exception e) { - Log.e(TAG, "startFido2Register error", e); - } - } - - /** - * 使用 Play Services FIDO2 发起认证(Get/Sign)流程。 - * - challengeB64: Base64 编码的 challenge - * - rpId: relying party id - */ - public void startFido2Sign(String challengeB64, String rpId) { - try { - byte[] challenge = Base64.decode(challengeB64, Base64.DEFAULT); - - PublicKeyCredentialRequestOptions options = new PublicKeyCredentialRequestOptions.Builder() - .setChallenge(challenge) - .setRpId(rpId) - .build(); - - Fido.getFido2ApiClient(this) - .getSignIntent(options) - .addOnSuccessListener(new OnSuccessListener() { - @Override - public void onSuccess(Object pending) { - try { - android.content.IntentSender sender = null; - if (pending == null) { - Log.w(TAG, "FIDO2 pending is null"); - } else if (pending instanceof PendingIntent) { - sender = ((PendingIntent) pending).getIntentSender(); - } else { - try { - java.lang.reflect.Method m = pending.getClass().getMethod("getIntentSender"); - Object o = m.invoke(pending); - if (o instanceof android.content.IntentSender) { - sender = (android.content.IntentSender) o; - } - } catch (NoSuchMethodException nsme) { - Log.w(TAG, "pending object has no getIntentSender method"); - } - } - if (sender != null) { - IntentSenderRequest req = new IntentSenderRequest.Builder(sender).build(); - fido2Launcher.launch(req); - } else { - Log.w(TAG, "FIDO2 sign pendingIntent has no IntentSender"); - } - } catch (Exception e) { - Log.e(TAG, "Failed to launch FIDO2 sign pending intent", e); - } - } - }) - .addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(Exception e) { - Log.e(TAG, "getSignIntent failed", e); - } - }); - } catch (Exception e) { - Log.e(TAG, "startFido2Sign error", e); - } - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - // 处理返回键:优先让 GeckoSession 后退(如果可行),否则最小化应用 - if (keyCode == KeyEvent.KEYCODE_BACK) { - if (geckoSession != null && mCanGoBack) { - geckoSession.goBack(); - } else { - moveTaskToBack(true); - } - return true; - } - return super.onKeyDown(keyCode, event); - } - - @Override - public void onBackPressed() { - // 与 onKeyDown 一致:可后退则后退,否则最小化 - if (geckoSession != null && mCanGoBack) { - geckoSession.goBack(); - } else { - moveTaskToBack(true); - } - } - - /** - * 返回用于注入到页面以模拟桌面环境的脚本(复用 WebViewActivity 的实现) - */ - private String getDesktopSpoofScript() { - return "(function() { try { Object.defineProperty(window.screen, 'width', {get: function() { return 1920; }, configurable: true});" + - "Object.defineProperty(window.screen, 'height', {get: function() { return 1080; }, configurable: true});" + - "Object.defineProperty(window.screen, 'availWidth', {get: function() { return 1920; }, configurable: true});" + - "Object.defineProperty(window.screen, 'availHeight', {get: function() { return 1080; }, configurable: true});" + - "Object.defineProperty(window, 'innerWidth', {get: function() { return 1920; }, configurable: true});" + - "Object.defineProperty(window, 'innerHeight', {get: function() { return 1080; }, configurable: true});" + - "Object.defineProperty(window, 'outerWidth', {get: function() { return 1920; }, configurable: true});" + - "Object.defineProperty(window, 'outerHeight', {get: function() { return 1080; }, configurable: true});" + - "Object.defineProperty(navigator, 'maxTouchPoints', {get: function() { return 0; }, configurable: true});" + - "Object.defineProperty(navigator, 'platform', {get: function() { return 'Win32'; }, configurable: true});" + - "if ('ontouchstart' in window) { try { delete window.ontouchstart; } catch(e){} }" + - "Object.defineProperty(navigator, 'webdriver', {get: function() { return undefined; }, configurable: true});" + - "Object.defineProperty(navigator, 'plugins', {get: function() { return [1,2,3,4,5]; }, configurable: true});" + - "Object.defineProperty(navigator, 'languages', {get: function() { return ['zh-CN','zh','en-US','en']; }, configurable: true});" + - "var viewport = document.querySelector('meta[name=viewport]'); if (viewport) { viewport.setAttribute('content', 'width=1920, initial-scale=1.0'); } else { var meta = document.createElement('meta'); meta.name='viewport'; meta.content='width=1920, initial-scale=1.0'; document.getElementsByTagName('head')[0].appendChild(meta); } } catch(e) {} })();"; - } - - @Override - protected void onDestroy() { - super.onDestroy(); - if (geckoSession != null) { - geckoSession.close(); - } - } -} diff --git a/app/src/main/java/com/example/simplebrowser/MainActivity.java b/app/src/main/java/com/example/simplebrowser/MainActivity.java index 5f4b6bd..147032e 100644 --- a/app/src/main/java/com/example/simplebrowser/MainActivity.java +++ b/app/src/main/java/com/example/simplebrowser/MainActivity.java @@ -67,47 +67,19 @@ protected void onCreate(Bundle savedInstanceState) { // WebView选项 RadioButton webViewOption = new RadioButton(this); - webViewOption.setText("内置浏览器 (Chromium)"); + webViewOption.setText("内置浏览器 (WebView)"); webViewOption.setId(2); webViewOption.setChecked(true); // 设为默认选项 browserModeGroup.addView(webViewOption); // WebView说明 TextView webViewDesc = new TextView(this); - webViewDesc.setText("✓ 完全全屏\n✓ Chromium内核\n⚠ WebAuthn受限"); + webViewDesc.setText("✓ 完全全屏\n✓ Chromium内核\n✓ 桌面模式支持"); webViewDesc.setTextSize(12); webViewDesc.setPadding(32, 4, 0, 8); - webViewDesc.setTextColor(0xFF2196F3); + webViewDesc.setTextColor(0xFF4CAF50); browserModeGroup.addView(webViewDesc); - // GeckoView选项(新增 - 推荐) - RadioButton geckoViewOption = new RadioButton(this); - geckoViewOption.setText("GeckoView (推荐)"); - geckoViewOption.setId(3); - browserModeGroup.addView(geckoViewOption); - - // GeckoView说明 - TextView geckoViewDesc = new TextView(this); - geckoViewDesc.setText("✓ 完全全屏\n✓ Firefox引擎\n✓ 完整WebAuthn支持\n✓ 现代Web标准"); - geckoViewDesc.setTextSize(12); - geckoViewDesc.setPadding(32, 4, 0, 8); - geckoViewDesc.setTextColor(0xFF4CAF50); // 绿色表示推荐 - browserModeGroup.addView(geckoViewDesc); - - // Chrome Custom Tabs选项 - RadioButton chromeTabsOption = new RadioButton(this); - chromeTabsOption.setText("Chrome Custom Tabs"); - chromeTabsOption.setId(1); - browserModeGroup.addView(chromeTabsOption); - - // 说明文字 - TextView chromeTabsDesc = new TextView(this); - chromeTabsDesc.setText("✓ 完美Cloudflare支持\n⚠ 非全屏"); - chromeTabsDesc.setTextSize(12); - chromeTabsDesc.setPadding(32, 4, 0, 8); - chromeTabsDesc.setTextColor(0xFF666666); - browserModeGroup.addView(chromeTabsDesc); - layout.addView(browserModeGroup); // 选择自定义图标(可选) @@ -150,17 +122,15 @@ protected void onCreate(Bundle savedInstanceState) { url = "https://" + url; } - // 获取选择的浏览器模式 - int selectedId = browserModeGroup.getCheckedRadioButtonId(); String finalUrl = url; if (userIconBitmap != null) { // 使用自定义图标,跳过图标抓取与兜底 - createShortcut(finalUrl, selectedId, userIconBitmap); + createShortcut(finalUrl, userIconBitmap); } else { // 显示进度提示并抓取网站图标 Toast.makeText(this, "正在获取网站图标...", Toast.LENGTH_SHORT).show(); - fetchFaviconAndCreateShortcut(finalUrl, selectedId); + fetchFaviconAndCreateShortcut(finalUrl); } } else { Toast.makeText(this, "请输入网址", Toast.LENGTH_SHORT).show(); @@ -170,7 +140,7 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(layout); } - private void fetchFaviconAndCreateShortcut(String url, int browserMode) { + private void fetchFaviconAndCreateShortcut(String url) { executorService.execute(() -> { Bitmap favicon = null; @@ -193,7 +163,7 @@ private void fetchFaviconAndCreateShortcut(String url, int browserMode) { } Bitmap finalFavicon = favicon; - mainHandler.post(() -> createShortcut(url, browserMode, finalFavicon)); + mainHandler.post(() -> createShortcut(url, finalFavicon)); }); } @@ -230,47 +200,22 @@ private Bitmap downloadFavicon(String faviconUrl) { return null; } - private void createShortcut(String url, int browserMode, Bitmap favicon) { + private void createShortcut(String url, Bitmap favicon) { android.content.pm.ShortcutManager shortcutManager = (android.content.pm.ShortcutManager) getSystemService(Context.SHORTCUT_SERVICE); - // 根据选择的模式创建Intent - Intent intent; - String modeLabel; - - switch (browserMode) { - case 1: // Chrome Custom Tabs - intent = new Intent(this, ChromeTabActivity.class) - .setAction(Intent.ACTION_VIEW) - .setData(android.net.Uri.parse(url)) - .putExtra("url", url); - modeLabel = " (Chrome)"; - break; - - case 3: // GeckoView - intent = new Intent(this, GeckoViewActivity.class) - .setAction(Intent.ACTION_VIEW) - .setData(android.net.Uri.parse(url)) - .putExtra("url", url); - modeLabel = " (GeckoView)"; - break; - - case 2: // WebView - default: - String uriString = url + "#desktopMode=true"; - intent = new Intent(this, WebViewActivity.class) - .setAction(Intent.ACTION_VIEW) - .setData(android.net.Uri.parse(uriString)) - .putExtra("url", url) - .putExtra("desktopMode", true); - modeLabel = " (WebView)"; - break; - } + // 创建WebView Intent + String uriString = url + "#desktopMode=true"; + Intent intent = new Intent(this, WebViewActivity.class) + .setAction(Intent.ACTION_VIEW) + .setData(android.net.Uri.parse(uriString)) + .putExtra("url", url) + .putExtra("desktopMode", true); android.content.pm.ShortcutInfo.Builder builder = new android.content.pm.ShortcutInfo.Builder(this, url + "_" + System.currentTimeMillis()) .setShortLabel("网页快捷方式") - .setLongLabel(url + modeLabel) + .setLongLabel(url + " (WebView)") .setIntent(intent); // 如果成功获取了favicon,使用它作为图标 @@ -285,20 +230,7 @@ private void createShortcut(String url, int browserMode, Bitmap favicon) { android.content.pm.ShortcutInfo shortcut = builder.build(); shortcutManager.requestPinShortcut(shortcut, null); - String modeName; - switch (browserMode) { - case 1: - modeName = "Chrome Custom Tabs"; - break; - case 3: - modeName = "GeckoView"; - break; - case 2: - default: - modeName = "WebView"; - break; - } - Toast.makeText(this, "快捷方式已创建 (" + modeName + ")", Toast.LENGTH_LONG).show(); + Toast.makeText(this, "快捷方式已创建 (WebView)", Toast.LENGTH_LONG).show(); } @Override diff --git a/build.gradle b/build.gradle index c82d173..68e2c19 100644 --- a/build.gradle +++ b/build.gradle @@ -12,8 +12,5 @@ allprojects { repositories { google() mavenCentral() - maven { - url "https://maven.mozilla.org/maven2/" - } } } \ No newline at end of file