diff --git a/app/src/main/java/com/fongmi/android/tv/Updater.java b/app/src/main/java/com/fongmi/android/tv/Updater.java index d111ac8ce9d..576c34e94f5 100644 --- a/app/src/main/java/com/fongmi/android/tv/Updater.java +++ b/app/src/main/java/com/fongmi/android/tv/Updater.java @@ -19,6 +19,8 @@ import com.fongmi.android.tv.utils.Notify; import com.fongmi.android.tv.utils.ResUtil; import com.fongmi.android.tv.utils.Task; +import com.fongmi.android.tv.utils.UpdateApkSource; +import com.fongmi.android.tv.utils.UpdateDownloadQueue; import com.github.catvod.net.OkHttp; import com.github.catvod.utils.Path; @@ -66,7 +68,7 @@ public class Updater implements Download.Callback, UpdateListener { private long lastTotal; private long lastSpeed; private long lastElapsed; - private boolean fallbackAttempted; + private UpdateDownloadQueue downloadQueue; private Updater() { } @@ -331,11 +333,11 @@ public void onConfirm(View view) { view.setEnabled(false); downloading = true; canceled = false; - fallbackAttempted = false; + downloadQueue = new UpdateDownloadQueue(UpdateApkSource.buildCandidates(selected.apkUrl, selected.fallbackApkUrl, Setting.getUpdateApkSource(), Setting.getUpdateApkCustomPrefix())); resetProgress(); Path.clear(getFile()); setDialogProgress(0, 0, selected.size, 0, 0); - startDownload(selected.apkUrl); + if (!startNextDownload()) finishDownloadError(ResUtil.getString(R.string.update_download_invalid)); } private void startDownload(String url) { @@ -343,21 +345,34 @@ private void startDownload(String url) { download.start(this); } - private boolean retryFallback() { - if (canceled || selected == null || fallbackAttempted || TextUtils.isEmpty(selected.fallbackApkUrl)) return false; - fallbackAttempted = true; + private boolean retryDownload() { + if (canceled || selected == null || downloadQueue == null) return false; Path.clear(getFile()); resetProgress(); setDialogProgress(0, 0, selected.size, 0, 0); - startDownload(selected.fallbackApkUrl); + return startNextDownload(); + } + + private boolean startNextDownload() { + String url = downloadQueue == null ? null : downloadQueue.next(); + if (TextUtils.isEmpty(url)) return false; + startDownload(url); return true; } + private void finishDownloadError(String message) { + downloading = false; + resetProgress(); + Notify.show(message); + dismiss(); + } + @Override public void onCancel(View view) { if (downloading) { canceled = true; downloading = false; + if (downloadQueue != null) downloadQueue.cancel(); if (download != null) download.cancel(); download = null; resetProgress(); @@ -418,11 +433,8 @@ private void setDialogProgress(int progress, long bytes, long total, long speed, public void error(String msg) { if (canceled) return; download = null; - if (retryFallback()) return; - downloading = false; - resetProgress(); - Notify.show(msg); - dismiss(); + if (retryDownload()) return; + finishDownloadError(msg); } @Override @@ -439,10 +451,8 @@ public void success(File file) { if (!TextUtils.isEmpty(error)) { Path.clear(file); downloading = true; - if (retryFallback()) return; - downloading = false; - Notify.show(error); - dismiss(); + if (retryDownload()) return; + finishDownloadError(error); return; } FileUtil.openFile(file); diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Backup.java b/app/src/main/java/com/fongmi/android/tv/bean/Backup.java index 657530e4566..e94107f6e08 100644 --- a/app/src/main/java/com/fongmi/android/tv/bean/Backup.java +++ b/app/src/main/java/com/fongmi/android/tv/bean/Backup.java @@ -33,7 +33,7 @@ public class Backup { public static final String PREF_WEB_HOME_EXTENSION = "web_home_extension"; public static final String PREF_WEB_HOME_EXTENSION_SOURCES = "web_home_extension_user_sources"; - private static final Set APP_PREFS = Set.of("doh", "ua", "wall", "wall_type", "reset", "site_mode", "site_block_keys", "search_column", "sync_mode", "sync_paths", "incognito", "drive_check", "drive_check_cache", "compact_episode_title", "web_home_fullscreen", "viewing_record_sync_enabled", "viewing_record_sync_local_write", "playback_remote_sync_config", "playback_webhook_config", "playback_webhook_privacy_accepted", "shell_proxy", "shell_proxy_rules", "shell_proxy_url", "shell_proxy_hosts", "update", "adblock", "zhuyin", "theme_color", "wall_color", "crash", "render", "pad_live_mode", "size", "scale", "buffer", "buffer_bytes", "back_buffer", "play_cache", "preload", "preload_threads", "preload_size", "preload_time", "player_auto_change", "background", "speed", "play_speed", "caption", "tunnel", "exo_4k_compat", "playback_performance_profile", "playback_performance_initialized", "perf_codec_async_queueing", "perf_dynamic_scheduling", "perf_video_duration_progress", "perf_late_drop_input", "perf_track_limit", "perf_adaptive_downgrade", "perf_load_only_selected_tracks", "perf_surface_fixed_size", "perf_decoder_fallback", "perf_soft_video_tune", "perf_high_buffer", "perf_bandwidth_meter", "player_button_order", "player_button_hidden", "audio_prefer", "video_prefer", "prefer_aac", "subtitle_text_size", "subtitle_position", "player_osd_title", "player_osd_resolution", "player_osd_time", "player_osd_progress", "player_osd_traffic", "player_osd_mini", "player_osd_diagnostics", "boot_live", "across", "change", "invert", "scale_live", "live_epg_url", "live_epg_history"); + private static final Set APP_PREFS = Set.of("doh", "ua", "wall", "wall_type", "reset", "site_mode", "site_block_keys", "search_column", "sync_mode", "sync_paths", "incognito", "drive_check", "drive_check_cache", "compact_episode_title", "web_home_fullscreen", "viewing_record_sync_enabled", "viewing_record_sync_local_write", "playback_remote_sync_config", "playback_webhook_config", "playback_webhook_privacy_accepted", "shell_proxy", "shell_proxy_rules", "shell_proxy_url", "shell_proxy_hosts", "update", "update_apk_source", "update_apk_custom_prefix", "adblock", "zhuyin", "theme_color", "wall_color", "crash", "render", "pad_live_mode", "size", "scale", "buffer", "buffer_bytes", "back_buffer", "play_cache", "preload", "preload_threads", "preload_size", "preload_time", "player_auto_change", "background", "speed", "play_speed", "caption", "tunnel", "exo_4k_compat", "playback_performance_profile", "playback_performance_initialized", "perf_codec_async_queueing", "perf_dynamic_scheduling", "perf_video_duration_progress", "perf_late_drop_input", "perf_track_limit", "perf_adaptive_downgrade", "perf_load_only_selected_tracks", "perf_surface_fixed_size", "perf_decoder_fallback", "perf_soft_video_tune", "perf_high_buffer", "perf_bandwidth_meter", "player_button_order", "player_button_hidden", "audio_prefer", "video_prefer", "prefer_aac", "subtitle_text_size", "subtitle_position", "player_osd_title", "player_osd_resolution", "player_osd_time", "player_osd_progress", "player_osd_traffic", "player_osd_mini", "player_osd_diagnostics", "boot_live", "across", "change", "invert", "scale_live", "live_epg_url", "live_epg_history"); @SerializedName("site") private List site; diff --git a/app/src/main/java/com/fongmi/android/tv/setting/Setting.java b/app/src/main/java/com/fongmi/android/tv/setting/Setting.java index c6b725c3454..1efd5443209 100644 --- a/app/src/main/java/com/fongmi/android/tv/setting/Setting.java +++ b/app/src/main/java/com/fongmi/android/tv/setting/Setting.java @@ -18,6 +18,7 @@ import com.fongmi.android.tv.App; import com.fongmi.android.tv.BuildConfig; import com.fongmi.android.tv.bean.Update; +import com.fongmi.android.tv.utils.UpdateApkSource; import com.fongmi.android.tv.utils.WebViewUtil; import com.github.catvod.crawler.DebugLogStore; import com.github.catvod.crawler.SpiderDebug; @@ -661,6 +662,22 @@ public static void putUpdateChannel(String channel) { Prefers.put("update_channel", Update.CHANNEL_BETA.equals(channel) ? Update.CHANNEL_BETA : Update.CHANNEL_STABLE); } + public static String getUpdateApkSource() { + return UpdateApkSource.normalizeSource(Prefers.getString("update_apk_source", UpdateApkSource.GH_PROXY)); + } + + public static void putUpdateApkSource(String source) { + Prefers.put("update_apk_source", UpdateApkSource.normalizeSource(source)); + } + + public static String getUpdateApkCustomPrefix() { + return UpdateApkSource.normalizeCustomPrefix(Prefers.getString("update_apk_custom_prefix")); + } + + public static void putUpdateApkCustomPrefix(String prefix) { + Prefers.put("update_apk_custom_prefix", UpdateApkSource.normalizeCustomPrefix(prefix)); + } + public static boolean isAdblock() { return Prefers.getBoolean("adblock", true); } diff --git a/app/src/main/java/com/fongmi/android/tv/ui/dialog/AboutDialog.java b/app/src/main/java/com/fongmi/android/tv/ui/dialog/AboutDialog.java index ebe27843a4d..565e187311c 100644 --- a/app/src/main/java/com/fongmi/android/tv/ui/dialog/AboutDialog.java +++ b/app/src/main/java/com/fongmi/android/tv/ui/dialog/AboutDialog.java @@ -28,6 +28,8 @@ public static void show(FragmentActivity activity, Runnable updateAction) { configureContentHeight(activity, binding); Dialog dialog = LightDialog.create(activity, null, binding.getRoot()); + binding.updateSource.setText(UpdateApkSourceDialog.summary(activity)); + binding.updateSource.setOnClickListener(v -> UpdateApkSourceDialog.show(activity, () -> binding.updateSource.setText(UpdateApkSourceDialog.summary(activity)))); binding.confirm.setOnClickListener(v -> dialog.dismiss()); binding.checkUpdate.setOnClickListener(v -> { dialog.dismiss(); diff --git a/app/src/main/java/com/fongmi/android/tv/ui/dialog/UpdateApkSourceDialog.java b/app/src/main/java/com/fongmi/android/tv/ui/dialog/UpdateApkSourceDialog.java new file mode 100644 index 00000000000..be54263face --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/ui/dialog/UpdateApkSourceDialog.java @@ -0,0 +1,86 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.app.Dialog; +import android.content.Context; +import android.text.InputType; +import android.view.LayoutInflater; + +import androidx.fragment.app.FragmentActivity; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.databinding.DialogUpdateApkSourceCustomBinding; +import com.fongmi.android.tv.setting.Setting; +import com.fongmi.android.tv.utils.UpdateApkSource; + +public final class UpdateApkSourceDialog { + + private static final String[] SOURCES = { + UpdateApkSource.GH_PROXY, + UpdateApkSource.GHFAST, + UpdateApkSource.DIRECT, + UpdateApkSource.CUSTOM + }; + + private UpdateApkSourceDialog() { + } + + public static void show(FragmentActivity activity, Runnable onChanged) { + CharSequence[] labels = { + activity.getString(R.string.update_apk_source_gh_proxy), + activity.getString(R.string.update_apk_source_ghfast), + activity.getString(R.string.update_apk_source_direct), + activity.getString(R.string.update_apk_source_custom) + }; + ChoiceDialog.showSingle(activity, R.string.update_apk_source_title, labels, sourceIndex(Setting.getUpdateApkSource()), which -> { + if (which < 0 || which >= SOURCES.length) return; + if (UpdateApkSource.CUSTOM.equals(SOURCES[which])) { + showCustom(activity, onChanged); + return; + } + Setting.putUpdateApkSource(SOURCES[which]); + if (onChanged != null) onChanged.run(); + }); + } + + public static String summary(Context context) { + int label = switch (Setting.getUpdateApkSource()) { + case UpdateApkSource.GHFAST -> R.string.update_apk_source_ghfast; + case UpdateApkSource.DIRECT -> R.string.update_apk_source_direct; + case UpdateApkSource.CUSTOM -> R.string.update_apk_source_custom; + default -> R.string.update_apk_source_gh_proxy; + }; + return context.getString(R.string.about_update_apk_source_value, context.getString(label)); + } + + private static int sourceIndex(String source) { + for (int i = 0; i < SOURCES.length; i++) if (SOURCES[i].equals(source)) return i; + return 0; + } + + private static void showCustom(FragmentActivity activity, Runnable onChanged) { + DialogUpdateApkSourceCustomBinding binding = DialogUpdateApkSourceCustomBinding.inflate(LayoutInflater.from(activity)); + binding.input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); + binding.input.setText(Setting.getUpdateApkCustomPrefix()); + binding.input.setSelection(binding.input.length()); + + Dialog dialog = LightDialog.create(activity, activity.getString(R.string.update_apk_source_custom_title), binding.getRoot()); + binding.cancel.setOnClickListener(view -> dialog.dismiss()); + binding.save.setOnClickListener(view -> { + String value = binding.input.getText() == null ? "" : binding.input.getText().toString(); + String prefix = UpdateApkSource.normalizeCustomPrefix(value); + if (prefix.isEmpty()) { + binding.inputLayout.setError(activity.getString(R.string.update_apk_source_custom_invalid)); + binding.input.requestFocus(); + return; + } + binding.inputLayout.setError(null); + Setting.putUpdateApkCustomPrefix(prefix); + Setting.putUpdateApkSource(UpdateApkSource.CUSTOM); + dialog.dismiss(); + if (onChanged != null) onChanged.run(); + }); + dialog.setCanceledOnTouchOutside(false); + dialog.show(); + binding.save.requestFocus(); + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/utils/UpdateApkSource.java b/app/src/main/java/com/fongmi/android/tv/utils/UpdateApkSource.java new file mode 100644 index 00000000000..5c801559d13 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/utils/UpdateApkSource.java @@ -0,0 +1,106 @@ +package com.fongmi.android.tv.utils; + +import java.net.URI; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +public final class UpdateApkSource { + + public static final String GH_PROXY = "gh_proxy"; + public static final String GHFAST = "ghfast"; + public static final String DIRECT = "direct"; + public static final String CUSTOM = "custom"; + + public static final String GH_PROXY_PREFIX = "https://gh-proxy.com/"; + public static final String GHFAST_PREFIX = "https://ghfast.top/"; + + private UpdateApkSource() { + } + + public static String normalizeSource(String source) { + if (GHFAST.equals(source) || DIRECT.equals(source) || CUSTOM.equals(source)) return source; + return GH_PROXY; + } + + public static String normalizeCustomPrefix(String value) { + if (value == null) return ""; + String prefix = value.trim(); + if (!isValidCustomPrefix(prefix)) return ""; + return prefix.replaceAll("/+$", "") + "/"; + } + + public static boolean isValidCustomPrefix(String value) { + if (value == null || value.isBlank()) return false; + try { + URI uri = URI.create(value.trim()); + String scheme = uri.getScheme(); + return ("http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme)) + && uri.getHost() != null + && !uri.getHost().isBlank() + && uri.getRawQuery() == null + && uri.getRawFragment() == null; + } catch (Exception e) { + return false; + } + } + + public static boolean isGithubUrl(String value) { + if (value == null || value.isBlank()) return false; + try { + URI uri = URI.create(value.trim()); + return "github.com".equalsIgnoreCase(uri.getHost()) + && ("http".equalsIgnoreCase(uri.getScheme()) || "https".equalsIgnoreCase(uri.getScheme())); + } catch (Exception e) { + return false; + } + } + + public static String applyPrefix(String prefix, String githubUrl) { + String normalized = normalizeCustomPrefix(prefix); + if (normalized.isEmpty() || !isGithubUrl(githubUrl)) return githubUrl; + return normalized + githubUrl.trim(); + } + + public static List buildCandidates(String primaryUrl, String fallbackUrl, String source, String customPrefix) { + Set github = new LinkedHashSet<>(); + Set other = new LinkedHashSet<>(); + collect(primaryUrl, github, other); + collect(fallbackUrl, github, other); + + Set candidates = new LinkedHashSet<>(); + for (String url : github) addGithubCandidates(candidates, url, normalizeSource(source), customPrefix); + candidates.addAll(other); + return new ArrayList<>(candidates); + } + + private static void collect(String value, Set github, Set other) { + if (value == null || value.isBlank()) return; + String url = value.trim(); + (isGithubUrl(url) ? github : other).add(url); + } + + private static void addGithubCandidates(Set candidates, String url, String source, String customPrefix) { + switch (source) { + case DIRECT -> candidates.add(url); + case GHFAST -> { + candidates.add(applyPrefix(GHFAST_PREFIX, url)); + candidates.add(applyPrefix(GH_PROXY_PREFIX, url)); + candidates.add(url); + } + case CUSTOM -> { + String custom = applyPrefix(customPrefix, url); + if (!url.equals(custom)) candidates.add(custom); + candidates.add(applyPrefix(GH_PROXY_PREFIX, url)); + candidates.add(applyPrefix(GHFAST_PREFIX, url)); + candidates.add(url); + } + default -> { + candidates.add(applyPrefix(GH_PROXY_PREFIX, url)); + candidates.add(applyPrefix(GHFAST_PREFIX, url)); + candidates.add(url); + } + } + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/utils/UpdateDownloadQueue.java b/app/src/main/java/com/fongmi/android/tv/utils/UpdateDownloadQueue.java new file mode 100644 index 00000000000..0337abbfcdf --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/utils/UpdateDownloadQueue.java @@ -0,0 +1,28 @@ +package com.fongmi.android.tv.utils; + +import java.util.ArrayList; +import java.util.List; + +public final class UpdateDownloadQueue { + + private final List candidates; + private int index = -1; + private boolean canceled; + + public UpdateDownloadQueue(List candidates) { + this.candidates = candidates == null ? List.of() : new ArrayList<>(candidates); + } + + public String next() { + if (canceled || index + 1 >= candidates.size()) return null; + return candidates.get(++index); + } + + public void cancel() { + canceled = true; + } + + public int size() { + return candidates.size(); + } +} diff --git a/app/src/main/res/layout/dialog_about.xml b/app/src/main/res/layout/dialog_about.xml index ee999e4bf9b..4587a911d4f 100644 --- a/app/src/main/res/layout/dialog_about.xml +++ b/app/src/main/res/layout/dialog_about.xml @@ -81,10 +81,26 @@ + + diff --git a/app/src/main/res/layout/dialog_update_apk_source_custom.xml b/app/src/main/res/layout/dialog_update_apk_source_custom.xml new file mode 100644 index 00000000000..8a41b14ae45 --- /dev/null +++ b/app/src/main/res/layout/dialog_update_apk_source_custom.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 7d82461042f..0cf644ff660 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -690,6 +690,16 @@ WebHomeTV 是基于开源生态二次开发的技术学习与研究项目,软件本体完全免费,不提供任何付费服务、影视内容、直播源、接口源、资源存储或内容分发能力。\n\n本软件仅供技术学习、研究和个人测试使用,请在下载、安装或试用后 24 小时内自行卸载。继续使用本软件所产生的一切行为及后果,由使用者自行承担。\n\n本软件不内置、不售卖、不传播任何影视资源,不对用户自行添加的接口、站源、插件、脚本、链接、网盘资源或第三方服务内容负责。使用者应遵守所在地法律法规,尊重版权方和内容提供方的合法权益,不得将本软件用于任何侵权、盗版、传播非法内容或其他违法违规用途。\n\n严禁任何个人或组织以本软件名义进行售卖、引流、收费维护、会员服务、广告变现、盒子预装、电视盒子捆绑销售或其他任何形式的获益行为。对于将本软件内置于电视盒子、机顶盒、付费套餐或商业服务中进行销售、推广的行为,项目方明确反对并予以谴责。\n\n如你不同意以上声明,请立即停止使用并卸载本软件。 检查更新 我已知悉 + APK 下载源 + APK 下载源:%1$s + APK 下载源 + gh-proxy.com + ghfast.top + GitHub 直连 + 自定义 + 自定义下载源 + URL 前缀,例如 https://proxy.example.com/ + 请输入不含查询参数和片段的有效 HTTP 或 HTTPS URL 前缀 选择备份 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index ef5bf8a2867..407ab515a2f 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -674,6 +674,16 @@ WebHomeTV 是基於開源生態二次開發的技術學習與研究項目,軟體本體完全免費,不提供任何付費服務、影視內容、直播源、接口源、資源儲存或內容分發能力。\n\n本軟體僅供技術學習、研究和個人測試使用,請在下載、安裝或試用後 24 小時內自行解除安裝。繼續使用本軟體所產生的一切行為及後果,由使用者自行承擔。\n\n本軟體不內置、不售賣、不傳播任何影視資源,不對使用者自行添加的接口、站源、插件、腳本、連結、網盤資源或第三方服務內容負責。使用者應遵守所在地法律法規,尊重版權方和內容提供方的合法權益,不得將本軟體用於任何侵權、盜版、傳播非法內容或其他違法違規用途。\n\n嚴禁任何個人或組織以本軟體名義進行售賣、引流、收費維護、會員服務、廣告變現、盒子預裝、電視盒子捆綁銷售或其他任何形式的獲益行為。對於將本軟體內置於電視盒子、機頂盒、付費套餐或商業服務中進行銷售、推廣的行為,項目方明確反對並予以譴責。\n\n如你不同意以上聲明,請立即停止使用並解除安裝本軟體。 檢查更新 我已知悉 + APK 下載源 + APK 下載源:%1$s + APK 下載源 + gh-proxy.com + ghfast.top + GitHub 直連 + 自訂 + 自訂下載源 + URL 前綴,例如 https://proxy.example.com/ + 請輸入不含查詢參數和片段的有效 HTTP 或 HTTPS URL 前綴 選擇備份 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a5c3d330528..7db023eea79 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -692,6 +692,16 @@ WebHomeTV is a technical learning and research project based on open source ecosystems. The software itself is completely free and does not provide paid services, video content, live sources, API sources, resource storage, or content distribution.\n\nThis software is only for technical learning, research, and personal testing. Please uninstall it within 24 hours after downloading, installing, or trying it. All behavior and consequences arising from continued use are borne by the user.\n\nThis software does not embed, sell, or distribute any video resources, and is not responsible for interfaces, sources, plugins, scripts, links, cloud-drive resources, or third-party service content added by users. Users must comply with local laws and regulations, respect the rights of copyright owners and content providers, and must not use this software for infringement, piracy, illegal content distribution, or other illegal purposes.\n\nAny individual or organization is strictly prohibited from selling, traffic diversion, paid maintenance, membership services, advertising monetization, box preinstallation, TV box bundle sales, or any other form of profit-making in the name of this software. The project clearly opposes and condemns selling or promoting this software as part of TV boxes, set-top boxes, paid packages, or commercial services.\n\nIf you do not agree with this statement, please stop using and uninstall this software immediately. Check updates I understand + APK download source + APK download source: %1$s + APK download source + gh-proxy.com + ghfast.top + GitHub direct + Custom + Custom download source + URL prefix, for example https://proxy.example.com/ + Enter a valid HTTP or HTTPS URL prefix without a query or fragment Select backup diff --git a/app/src/test/java/com/fongmi/android/tv/bean/BackupPreferenceFilterTest.java b/app/src/test/java/com/fongmi/android/tv/bean/BackupPreferenceFilterTest.java index 5545d263cca..9b9f69a4939 100644 --- a/app/src/test/java/com/fongmi/android/tv/bean/BackupPreferenceFilterTest.java +++ b/app/src/test/java/com/fongmi/android/tv/bean/BackupPreferenceFilterTest.java @@ -46,4 +46,15 @@ public void manifestCountCoversSourcesSwitchAndPerExtensionState() { assertEquals(3, backup.getWebHomeExtensionPreferenceCount()); assertEquals(2, backup.getWebHomeExtensionSourceCount()); } + + @Test + public void updateDownloadSourcePreferencesAreAppSettings() { + SyncOptions settingsOnly = new SyncOptions().config(false).spider(false).webHome(false).settings(true); + SyncOptions spiderOnly = new SyncOptions().config(false).spider(true).webHome(false).settings(false); + + assertTrue(Backup.include("update_apk_source", settingsOnly)); + assertTrue(Backup.include("update_apk_custom_prefix", settingsOnly)); + assertFalse(Backup.include("update_apk_source", spiderOnly)); + assertFalse(Backup.include("update_apk_custom_prefix", spiderOnly)); + } } diff --git a/app/src/test/java/com/fongmi/android/tv/utils/UpdateApkSourceTest.java b/app/src/test/java/com/fongmi/android/tv/utils/UpdateApkSourceTest.java new file mode 100644 index 00000000000..ec30bb2d6b4 --- /dev/null +++ b/app/src/test/java/com/fongmi/android/tv/utils/UpdateApkSourceTest.java @@ -0,0 +1,76 @@ +package com.fongmi.android.tv.utils; + +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class UpdateApkSourceTest { + + private static final String GITHUB = "https://github.com/fish2018/webhtv/releases/download/v1/mobile.apk"; + private static final String CNB = "https://cnb.cool/fish2035/webhtv-release/-/releases/download/v1/mobile.apk"; + + @Test + public void defaultsUnknownSourceToGhProxy() { + assertEquals(UpdateApkSource.GH_PROXY, UpdateApkSource.normalizeSource(null)); + assertEquals(UpdateApkSource.GH_PROXY, UpdateApkSource.normalizeSource("unknown")); + } + + @Test + public void ordersBuiltInSourcesAndDirectFallback() { + assertEquals(List.of( + "https://gh-proxy.com/" + GITHUB, + "https://ghfast.top/" + GITHUB, + GITHUB + ), UpdateApkSource.buildCandidates(GITHUB, null, UpdateApkSource.GH_PROXY, "")); + + assertEquals(List.of( + "https://ghfast.top/" + GITHUB, + "https://gh-proxy.com/" + GITHUB, + GITHUB + ), UpdateApkSource.buildCandidates(GITHUB, null, UpdateApkSource.GHFAST, "")); + } + + @Test + public void directModeDoesNotUseThirdPartySources() { + assertEquals(List.of(GITHUB), UpdateApkSource.buildCandidates(GITHUB, null, UpdateApkSource.DIRECT, "")); + } + + @Test + public void customSourcePrecedesBuiltInsAndNormalizesSlash() { + assertEquals("https://proxy.example.com/path/", UpdateApkSource.normalizeCustomPrefix(" https://proxy.example.com/path/// ")); + assertEquals(List.of( + "https://proxy.example.com/path/" + GITHUB, + "https://gh-proxy.com/" + GITHUB, + "https://ghfast.top/" + GITHUB, + GITHUB + ), UpdateApkSource.buildCandidates(GITHUB, null, UpdateApkSource.CUSTOM, "https://proxy.example.com/path///")); + } + + @Test + public void rejectsInvalidCustomPrefixes() { + assertFalse(UpdateApkSource.isValidCustomPrefix("proxy.example.com")); + assertFalse(UpdateApkSource.isValidCustomPrefix("https://proxy.example.com/?token=x")); + assertFalse(UpdateApkSource.isValidCustomPrefix("https://proxy.example.com/#part")); + assertTrue(UpdateApkSource.isValidCustomPrefix("http://192.168.1.2:8080/proxy")); + } + + @Test + public void prioritizesGithubCandidatesAndKeepsNonGithubFallback() { + assertEquals(List.of( + "https://gh-proxy.com/" + GITHUB, + "https://ghfast.top/" + GITHUB, + GITHUB, + CNB + ), UpdateApkSource.buildCandidates(CNB, GITHUB, UpdateApkSource.GH_PROXY, "")); + } + + @Test + public void leavesNonGithubUrlsUntouchedAndDeduplicatesCandidates() { + assertFalse(UpdateApkSource.isGithubUrl("https://api.github.com/repos/fish2018/webhtv")); + assertEquals(List.of(CNB), UpdateApkSource.buildCandidates(CNB, CNB, UpdateApkSource.GH_PROXY, "")); + } +} diff --git a/app/src/test/java/com/fongmi/android/tv/utils/UpdateDownloadQueueTest.java b/app/src/test/java/com/fongmi/android/tv/utils/UpdateDownloadQueueTest.java new file mode 100644 index 00000000000..af5fba4f021 --- /dev/null +++ b/app/src/test/java/com/fongmi/android/tv/utils/UpdateDownloadQueueTest.java @@ -0,0 +1,31 @@ +package com.fongmi.android.tv.utils; + +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class UpdateDownloadQueueTest { + + @Test + public void advancesAfterEachFailedAttemptAndStopsAtEnd() { + UpdateDownloadQueue queue = new UpdateDownloadQueue(List.of("first", "second", "direct")); + + assertEquals(3, queue.size()); + assertEquals("first", queue.next()); + assertEquals("second", queue.next()); + assertEquals("direct", queue.next()); + assertNull(queue.next()); + } + + @Test + public void cancelPreventsFurtherRetries() { + UpdateDownloadQueue queue = new UpdateDownloadQueue(List.of("first", "second")); + + assertEquals("first", queue.next()); + queue.cancel(); + assertNull(queue.next()); + } +}