diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9e7a740..f969dea 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -191,9 +191,6 @@ - diff --git a/app/src/main/java/io/nekohasekai/sagernet/Constants.kt b/app/src/main/java/io/nekohasekai/sagernet/Constants.kt index a2fece3..d44a3b6 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/Constants.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/Constants.kt @@ -147,7 +147,6 @@ object Key { // - const val NEKO_PLUGIN_MANAGED = "nekoPlugins" const val APP_TLS_VERSION = "appTLSVersion" const val ENABLE_CLASH_API = "enableClashAPI" } diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/VpnService.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/VpnService.kt index c43b250..751c149 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/bg/VpnService.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/bg/VpnService.kt @@ -16,9 +16,6 @@ import io.nekohasekai.sagernet.fmt.hysteria.HysteriaBean import io.nekohasekai.sagernet.ktx.* import io.nekohasekai.sagernet.ui.VpnRequestActivity import io.nekohasekai.sagernet.utils.Subnet -import libcore.* -import moe.matsuri.nb4a.net.LocalResolverImpl -import moe.matsuri.nb4a.proxy.neko.needBypassRootUid import android.net.VpnService as BaseVpnService class VpnService : BaseVpnService(), @@ -135,7 +132,7 @@ class VpnService : BaseVpnService(), var bypass = DataStore.bypass val workaroundSYSTEM = false /* DataStore.tunImplementation == TunImplementation.SYSTEM */ val needBypassRootUid = workaroundSYSTEM || data.proxy!!.config.trafficMap.values.any { - it[0].nekoBean?.needBypassRootUid() == true || it[0].hysteriaBean?.protocol == HysteriaBean.PROTOCOL_FAKETCP + it[0].hysteriaBean?.protocol == HysteriaBean.PROTOCOL_FAKETCP } if (proxyApps || needBypassRootUid) { diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/proto/BoxInstance.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/BoxInstance.kt index bca90d0..84586bc 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/bg/proto/BoxInstance.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/BoxInstance.kt @@ -21,11 +21,6 @@ import io.nekohasekai.sagernet.plugin.PluginManager import kotlinx.coroutines.* import libcore.BoxInstance import libcore.Libcore -import moe.matsuri.nb4a.plugin.NekoPluginManager -import moe.matsuri.nb4a.proxy.neko.NekoBean -import moe.matsuri.nb4a.proxy.neko.NekoJSInterface -import moe.matsuri.nb4a.proxy.neko.updateAllConfig -import org.json.JSONObject import java.io.File abstract class BoxInstance( @@ -53,7 +48,6 @@ abstract class BoxInstance( } protected open suspend fun loadConfig() { - NekoJSInterface.Default.destroyAllJsi() box = Libcore.newSingBoxInstance(config.config) } @@ -88,17 +82,6 @@ abstract class BoxInstance( } } } - - is NekoBean -> { - // check if plugin binary can be loaded - initPlugin(bean.plgId) - - // build config and check if succeed - bean.updateAllConfig(port) - if (bean.allConfig == null) { - throw NekoPluginManager.PluginInternalException(bean.protocolId) - } - } } } } @@ -211,44 +194,6 @@ abstract class BoxInstance( processes.start(commands) } - - bean is NekoBean -> { - // config built from JS - val nekoRunConfigs = bean.allConfig.optJSONArray("nekoRunConfigs") - val configs = mutableMapOf() - - nekoRunConfigs?.forEach { _, any -> - any as JSONObject - - val name = any.getString("name") - val configFile = File(cacheDir, name) - configFile.parentFile?.mkdirs() - val content = any.getString("content") - configFile.writeText(content) - - cacheFiles.add(configFile) - configs[name] = configFile.absolutePath - - Logs.d(name + "\n\n" + content) - } - - val nekoCommands = bean.allConfig.getJSONArray("nekoCommands") - val commands = mutableListOf() - - nekoCommands.forEach { _, any -> - if (any is String) { - if (configs.containsKey(any)) { - commands.add(configs[any]!!) - } else if (any == "%exe%") { - commands.add(initPlugin(bean.plgId).path) - } else { - commands.add(any) - } - } - } - - processes.start(commands) - } } } } diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt b/app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt index 8c95911..df6f06a 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt @@ -81,7 +81,6 @@ object DataStore : OnPreferenceDataStoreChangeListener { return groups.find { it.type == GroupType.BASIC }!!.id } - var nekoPlugins by configurationStore.string(Key.NEKO_PLUGIN_MANAGED) var appTLSVersion by configurationStore.string(Key.APP_TLS_VERSION) var enableClashAPI by configurationStore.boolean(Key.ENABLE_CLASH_API) var showBottomBar by configurationStore.boolean(Key.SHOW_BOTTOM_BAR) diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt b/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt index b53343d..b975695 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt @@ -239,7 +239,7 @@ data class ProxyEntity( is SSHBean -> false is WireGuardBean -> false is ShadowTLSBean -> false - is NekoBean -> nekoBean!!.haveStandardLink() + is NekoBean -> false is ConfigBean -> false else -> true } @@ -257,7 +257,7 @@ data class ProxyEntity( is HysteriaBean -> toUri() is TuicBean -> toUri() is AnyTLSBean -> toUri() - is NekoBean -> shareLink() + is NekoBean -> "" else -> toUniversalLink() } } @@ -470,7 +470,6 @@ data class ProxyEntity( TYPE_SHADOWTLS -> ShadowTLSSettingsActivity::class.java TYPE_ANYTLS -> AnyTLSSettingsActivity::class.java TYPE_CHAIN -> ChainSettingsActivity::class.java - TYPE_NEKO -> NekoSettingActivity::class.java TYPE_CONFIG -> ConfigSettingActivity::class.java else -> throw IllegalArgumentException() } diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/StandardV2RayBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/StandardV2RayBean.java index b3cbcc4..0e76e72 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/StandardV2RayBean.java +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/StandardV2RayBean.java @@ -54,11 +54,6 @@ public abstract class StandardV2RayBean extends AbstractBean { public String echConfig; - // sing-box 不再使用 - public Boolean enablePqSignature; - - public Boolean disabledDRS; - // --------------------------------------- Mux public Boolean enableMux; @@ -108,8 +103,6 @@ public abstract class StandardV2RayBean extends AbstractBean { if (enableECH == null) enableECH = false; if (JavaUtil.isNullOrBlank(echConfig)) echConfig = ""; - if (enablePqSignature == null) enablePqSignature = false; - if (disabledDRS == null) disabledDRS = false; if (enableMux == null) enableMux = false; if (muxPadding == null) muxPadding = false; @@ -119,7 +112,7 @@ public abstract class StandardV2RayBean extends AbstractBean { @Override public void serialize(ByteBufferOutput output) { - output.writeInt(2); + output.writeInt(3); super.serialize(output); output.writeString(uuid); output.writeString(encryption); @@ -167,11 +160,7 @@ public abstract class StandardV2RayBean extends AbstractBean { } output.writeBoolean(enableECH); - if (enableECH) { - output.writeBoolean(enablePqSignature); - output.writeBoolean(disabledDRS); - output.writeString(echConfig); - } + output.writeString(echConfig); output.writeInt(packetEncoding); @@ -229,16 +218,18 @@ public abstract class StandardV2RayBean extends AbstractBean { realityShortId = input.readString(); } - if (version >= 1) { // 从老版本升级上来 + if (version >= 1) { enableECH = input.readBoolean(); - if (enableECH) { - enablePqSignature = input.readBoolean(); - disabledDRS = input.readBoolean(); + if (version >= 3) { echConfig = input.readString(); + } else { + if (enableECH) { + input.readBoolean(); + input.readBoolean(); + echConfig = input.readString(); + } } - } - - if (version == 0) { + } else if (version == 0) { // 从老版本升级上来但是 version == 0, 可能有 enableECH 也可能没有,需要做判断 int position = input.getByteBuffer().position(); // 当前位置 @@ -250,8 +241,8 @@ public abstract class StandardV2RayBean extends AbstractBean { if (tmpPacketEncoding != 1 && tmpPacketEncoding != 2) { enableECH = tmpEnableECH; if (enableECH) { - enablePqSignature = input.readBoolean(); - disabledDRS = input.readBoolean(); + input.readBoolean(); + input.readBoolean(); echConfig = input.readString(); } } // 否则后一位就是 packetEncoding @@ -275,7 +266,6 @@ public abstract class StandardV2RayBean extends AbstractBean { bean.utlsFingerprint = utlsFingerprint; bean.packetEncoding = packetEncoding; bean.enableECH = enableECH; - bean.disabledDRS = disabledDRS; bean.echConfig = echConfig; bean.enableMux = enableMux; bean.muxPadding = muxPadding; diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/VMessBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/VMessBean.java index 84044da..2b7e8ae 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/VMessBean.java +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/VMessBean.java @@ -16,7 +16,12 @@ public class VMessBean extends StandardV2RayBean { super.initializeDefaultValues(); alterId = alterId != null ? alterId : 0; - encryption = JavaUtil.isNotBlank(encryption) ? encryption : "auto"; + + if (alterId == -1) { + encryption = JavaUtil.isNotBlank(encryption) ? encryption : ""; + } else { + encryption = JavaUtil.isNotBlank(encryption) ? encryption : "auto"; + } } @NotNull diff --git a/app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt b/app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt index 3fc8573..3774719 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt @@ -14,10 +14,7 @@ import io.nekohasekai.sagernet.fmt.trojan.parseTrojan import io.nekohasekai.sagernet.fmt.tuic.parseTuic import io.nekohasekai.sagernet.fmt.trojan_go.parseTrojanGo import io.nekohasekai.sagernet.fmt.v2ray.parseV2Ray -import moe.matsuri.nb4a.plugin.NekoPluginManager import moe.matsuri.nb4a.proxy.anytls.parseAnytls -import moe.matsuri.nb4a.proxy.neko.NekoJSInterface -import moe.matsuri.nb4a.proxy.neko.parseShareLink import moe.matsuri.nb4a.utils.JavaUtil.gson import moe.matsuri.nb4a.utils.Util import org.json.JSONArray @@ -112,7 +109,7 @@ suspend fun parseProxies(text: String): List { val entities = ArrayList() val entitiesByLine = ArrayList() - suspend fun String.parseLink(entities: ArrayList) { + fun String.parseLink(entities: ArrayList) { if (startsWith("clash://install-config?") || startsWith("sn://subscription?")) { throw SubscriptionFoundException(this) } @@ -211,22 +208,6 @@ suspend fun parseProxies(text: String): List { }.onFailure { Logs.w(it) } - } else { // Neko Plugins - NekoPluginManager.getProtocols().forEach { obj -> - obj.protocolConfig.optJSONArray("links")?.forEach { _, any -> - if (any is String && startsWith(any)) { - runCatching { - entities.add( - parseShareLink( - obj.plgId, obj.protocolId, this@parseLink - ) - ) - }.onFailure { - Logs.w(it) - } - } - } - } } } @@ -246,7 +227,6 @@ suspend fun parseProxies(text: String): List { } } } - NekoJSInterface.Default.destroyAllJsi() return if (entities.size > entitiesByLine.size) entities else entitiesByLine } diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/AboutFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/AboutFragment.kt index ba70fc3..54ed72a 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/AboutFragment.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/AboutFragment.kt @@ -75,9 +75,11 @@ class AboutFragment : ToolbarFragment(R.layout.layout_about) { } return MaterialAboutList.Builder() - .addCard(MaterialAboutCard.Builder() + .addCard( + MaterialAboutCard.Builder() .outline(false) - .addItem(MaterialAboutActionItem.Builder() + .addItem( + MaterialAboutActionItem.Builder() .icon(R.drawable.ic_baseline_update_24) .text(R.string.app_version) .subText(versionName) @@ -87,13 +89,15 @@ class AboutFragment : ToolbarFragment(R.layout.layout_about) { ) } .build()) - .addItem(MaterialAboutActionItem.Builder() + .addItem( + MaterialAboutActionItem.Builder() .icon(R.drawable.ic_baseline_layers_24) .text(getString(R.string.version_x, "sing-box")) .subText(Libcore.versionBox()) .setOnClickAction { } .build()) - .addItem(MaterialAboutActionItem.Builder() + .addItem( + MaterialAboutActionItem.Builder() .icon(R.drawable.ic_baseline_card_giftcard_24) .text(R.string.donate) .subText(R.string.donate_info) @@ -107,9 +111,11 @@ class AboutFragment : ToolbarFragment(R.layout.layout_about) { PackageCache.awaitLoadSync() for ((_, pkg) in PackageCache.installedPluginPackages) { try { - val pluginId = pkg.providers?.get(0)?.loadString(Plugins.METADATA_KEY_ID) - if (pluginId.isNullOrBlank() || pluginId.startsWith(Plugins.AUTHORITIES_PREFIX_NEKO_PLUGIN)) continue - addItem(MaterialAboutActionItem.Builder() + val pluginId = + pkg.providers?.get(0)?.loadString(Plugins.METADATA_KEY_ID) + if (pluginId.isNullOrBlank()) continue + addItem( + MaterialAboutActionItem.Builder() .icon(R.drawable.ic_baseline_nfc_24) .text( getString( @@ -137,7 +143,8 @@ class AboutFragment : ToolbarFragment(R.layout.layout_about) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { val pm = app.getSystemService(Context.POWER_SERVICE) as PowerManager if (!pm.isIgnoringBatteryOptimizations(app.packageName)) { - addItem(MaterialAboutActionItem.Builder() + addItem( + MaterialAboutActionItem.Builder() .icon(R.drawable.ic_baseline_running_with_errors_24) .text(R.string.ignore_battery_optimizations) .subText(R.string.ignore_battery_optimizations_sum) @@ -154,10 +161,12 @@ class AboutFragment : ToolbarFragment(R.layout.layout_about) { } } .build()) - .addCard(MaterialAboutCard.Builder() + .addCard( + MaterialAboutCard.Builder() .outline(false) .title(R.string.project) - .addItem(MaterialAboutActionItem.Builder() + .addItem( + MaterialAboutActionItem.Builder() .icon(R.drawable.ic_baseline_sanitizer_24) .text(R.string.github) .setOnClickAction { @@ -167,7 +176,8 @@ class AboutFragment : ToolbarFragment(R.layout.layout_about) { ) } .build()) - .addItem(MaterialAboutActionItem.Builder() + .addItem( + MaterialAboutActionItem.Builder() .icon(R.drawable.ic_qu_shadowsocks_foreground) .text(R.string.telegram) .setOnClickAction { diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/AppListActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/AppListActivity.kt index 6c96bc1..436151a 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/AppListActivity.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/AppListActivity.kt @@ -15,12 +15,9 @@ import android.view.ViewGroup import android.widget.Filter import android.widget.Filterable import androidx.annotation.UiThread -import androidx.appcompat.content.res.AppCompatResources import androidx.core.util.contains import androidx.core.util.set import androidx.core.view.ViewCompat -import androidx.core.view.isGone -import androidx.core.view.isVisible import androidx.core.widget.addTextChangedListener import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.DefaultItemAnimator @@ -29,27 +26,20 @@ import androidx.recyclerview.widget.RecyclerView import com.google.android.material.snackbar.Snackbar import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView import io.nekohasekai.sagernet.BuildConfig -import io.nekohasekai.sagernet.Key import io.nekohasekai.sagernet.R import io.nekohasekai.sagernet.SagerNet import io.nekohasekai.sagernet.database.DataStore import io.nekohasekai.sagernet.databinding.LayoutAppListBinding import io.nekohasekai.sagernet.databinding.LayoutAppsItemBinding import io.nekohasekai.sagernet.ktx.crossFadeFrom -import io.nekohasekai.sagernet.ktx.launchCustomTab import io.nekohasekai.sagernet.ktx.onMainDispatcher import io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher -import io.nekohasekai.sagernet.ktx.runOnIoDispatcher import io.nekohasekai.sagernet.utils.PackageCache import io.nekohasekai.sagernet.widget.ListListener import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.ensureActive import kotlinx.coroutines.withContext -import moe.matsuri.nb4a.plugin.NekoPluginManager -import moe.matsuri.nb4a.plugin.Plugins -import moe.matsuri.nb4a.proxy.neko.NekoJSInterface -import moe.matsuri.nb4a.ui.Dialogs import kotlin.coroutines.coroutineContext class AppListActivity : ThemedActivity() { @@ -81,35 +71,7 @@ class AppListActivity : ThemedActivity() { item = app binding.itemicon.setImageDrawable(app.icon) binding.title.text = app.name - if (forNeko) { - val packageName = app.packageName - val ver = getCachedApps()[packageName]?.versionName ?: "" - binding.desc.text = "$packageName ($ver)" - // - binding.button.isVisible = true - binding.button.setImageDrawable( - AppCompatResources.getDrawable( - this@AppListActivity, - R.drawable.ic_baseline_info_24 - ) - ) - binding.button.setOnClickListener { - runOnIoDispatcher { - val jsi = NekoJSInterface(packageName) - jsi.init() - val about = jsi.getAbout() - jsi.destorySuspend() - Dialogs.message( - this@AppListActivity, app.name as String, - "PackageName: ${packageName}\n" + - "Version: ${ver}\n" + - "--------\n" + about - ) - } - } - } else { - binding.desc.text = "${app.packageName} (${app.uid})" - } + binding.desc.text = "${app.packageName} (${app.uid})" handlePayload(listOf(SWITCH)) } @@ -117,7 +79,6 @@ class AppListActivity : ThemedActivity() { if (payloads.contains(SWITCH)) { val selected = isProxiedApp(item) binding.itemcheck.isChecked = selected - binding.button.isVisible = forNeko && selected } } @@ -125,23 +86,6 @@ class AppListActivity : ThemedActivity() { if (isProxiedApp(item)) proxiedUids.delete(item.uid) else proxiedUids[item.uid] = true DataStore.routePackages = apps.filter { isProxiedApp(it) } .joinToString("\n") { it.packageName } - - if (forNeko) { - if (isProxiedApp(item)) { - runOnIoDispatcher { - try { - NekoPluginManager.installPlugin(item.packageName) - } catch (e: Exception) { - // failed UI - runOnUiThread { onClick(v) } - Dialogs.logExceptionAndShow(this@AppListActivity, e) { } - } - } - } else { - NekoPluginManager.removeManagedPlugin(item.packageName) - } - } - appsAdapter.notifyItemRangeChanged(0, appsAdapter.itemCount, SWITCH) } } @@ -234,11 +178,8 @@ class AppListActivity : ThemedActivity() { } } - private var forNeko = false - fun getCachedApps(): MutableMap { - val packages = - if (forNeko) PackageCache.installedPluginPackages else PackageCache.installedPackages + val packages = PackageCache.installedPackages return packages.toMutableMap().apply { remove(BuildConfig.APPLICATION_ID) } @@ -246,7 +187,6 @@ class AppListActivity : ThemedActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - forNeko = intent?.hasExtra(Key.NEKO_PLUGIN_MANAGED) == true binding = LayoutAppListBinding.inflate(layoutInflater) setContentView(binding.root) @@ -275,28 +215,13 @@ class AppListActivity : ThemedActivity() { appsAdapter.filter.filter(binding.search.text?.toString() ?: "") } - if (forNeko) { - DataStore.routePackages = DataStore.nekoPlugins - binding.search.setText(Plugins.AUTHORITIES_PREFIX_NEKO_PLUGIN) - } - - binding.searchLayout.isGone = forNeko - binding.hintNekoPlugin.isGone = !forNeko - binding.actionLearnMore.setOnClickListener { - launchCustomTab("https://matsuridayo.github.io/m-plugin/") - } - loadApps() } private var sysApps = false override fun onCreateOptionsMenu(menu: Menu): Boolean { - if (forNeko) { - menuInflater.inflate(R.menu.app_list_neko_menu, menu) - } else { - menuInflater.inflate(R.menu.app_list_menu, menu) - } + menuInflater.inflate(R.menu.app_list_menu, menu) return true } @@ -368,9 +293,6 @@ class AppListActivity : ThemedActivity() { proxiedUids.clear() DataStore.routePackages = "" apps = apps.sortedWith(compareBy({ !isProxiedApp(it) }, { it.name.toString() })) - NekoPluginManager.plugins.forEach { - NekoPluginManager.removeManagedPlugin(it) - } onMainDispatcher { appsAdapter.notifyItemRangeChanged(0, appsAdapter.itemCount, SWITCH) } @@ -394,7 +316,6 @@ class AppListActivity : ThemedActivity() { override fun onDestroy() { loader?.cancel() - if (forNeko) DataStore.nekoPlugins = DataStore.routePackages super.onDestroy() } } diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt index f82edad..d915244 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt @@ -10,12 +10,15 @@ import android.text.SpannableStringBuilder import android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE import android.text.format.Formatter import android.text.style.ForegroundColorSpan -import android.view.* +import android.view.KeyEvent +import android.view.LayoutInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView import androidx.activity.result.contract.ActivityResultContracts -import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.Toolbar @@ -32,43 +35,82 @@ import androidx.viewpager2.widget.ViewPager2 import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator -import io.nekohasekai.sagernet.* +import io.nekohasekai.sagernet.GroupOrder +import io.nekohasekai.sagernet.GroupType +import io.nekohasekai.sagernet.Key +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.SagerNet import io.nekohasekai.sagernet.aidl.TrafficData import io.nekohasekai.sagernet.bg.BaseService import io.nekohasekai.sagernet.bg.proto.UrlTest -import io.nekohasekai.sagernet.database.* +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.database.GroupManager +import io.nekohasekai.sagernet.database.ProfileManager +import io.nekohasekai.sagernet.database.ProxyEntity +import io.nekohasekai.sagernet.database.ProxyGroup +import io.nekohasekai.sagernet.database.SagerDatabase import io.nekohasekai.sagernet.database.preference.OnPreferenceDataStoreChangeListener -import io.nekohasekai.sagernet.databinding.LayoutAppsItemBinding import io.nekohasekai.sagernet.databinding.LayoutProfileListBinding import io.nekohasekai.sagernet.databinding.LayoutProgressListBinding import io.nekohasekai.sagernet.fmt.AbstractBean import io.nekohasekai.sagernet.fmt.toUniversalLink import io.nekohasekai.sagernet.group.GroupUpdater import io.nekohasekai.sagernet.group.RawUpdater -import io.nekohasekai.sagernet.ktx.* +import io.nekohasekai.sagernet.ktx.FixedLinearLayoutManager +import io.nekohasekai.sagernet.ktx.Logs +import io.nekohasekai.sagernet.ktx.SubscriptionFoundException +import io.nekohasekai.sagernet.ktx.alert +import io.nekohasekai.sagernet.ktx.app +import io.nekohasekai.sagernet.ktx.dp2px +import io.nekohasekai.sagernet.ktx.getColorAttr +import io.nekohasekai.sagernet.ktx.getColour +import io.nekohasekai.sagernet.ktx.isIpAddress +import io.nekohasekai.sagernet.ktx.onMainDispatcher +import io.nekohasekai.sagernet.ktx.readableMessage +import io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher +import io.nekohasekai.sagernet.ktx.runOnLifecycleDispatcher +import io.nekohasekai.sagernet.ktx.runOnMainDispatcher +import io.nekohasekai.sagernet.ktx.scrollTo +import io.nekohasekai.sagernet.ktx.showAllowingStateLoss +import io.nekohasekai.sagernet.ktx.snackbar +import io.nekohasekai.sagernet.ktx.startFilesForResult +import io.nekohasekai.sagernet.ktx.tryToShow import io.nekohasekai.sagernet.plugin.PluginManager -import io.nekohasekai.sagernet.ui.profile.* -import io.nekohasekai.sagernet.utils.PackageCache +import io.nekohasekai.sagernet.ui.profile.ChainSettingsActivity +import io.nekohasekai.sagernet.ui.profile.HttpSettingsActivity +import io.nekohasekai.sagernet.ui.profile.HysteriaSettingsActivity +import io.nekohasekai.sagernet.ui.profile.MieruSettingsActivity +import io.nekohasekai.sagernet.ui.profile.NaiveSettingsActivity +import io.nekohasekai.sagernet.ui.profile.SSHSettingsActivity +import io.nekohasekai.sagernet.ui.profile.ShadowsocksSettingsActivity +import io.nekohasekai.sagernet.ui.profile.SocksSettingsActivity +import io.nekohasekai.sagernet.ui.profile.TrojanGoSettingsActivity +import io.nekohasekai.sagernet.ui.profile.TrojanSettingsActivity +import io.nekohasekai.sagernet.ui.profile.TuicSettingsActivity +import io.nekohasekai.sagernet.ui.profile.VMessSettingsActivity +import io.nekohasekai.sagernet.ui.profile.WireGuardSettingsActivity import io.nekohasekai.sagernet.widget.QRCodeDialog import io.nekohasekai.sagernet.widget.UndoSnackbarManager -import kotlinx.coroutines.* +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.newFixedThreadPoolContext import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import moe.matsuri.nb4a.Protocols import moe.matsuri.nb4a.Protocols.getProtocolColor -import moe.matsuri.nb4a.plugin.NekoPluginManager import moe.matsuri.nb4a.proxy.anytls.AnyTLSSettingsActivity import moe.matsuri.nb4a.proxy.config.ConfigSettingActivity -import moe.matsuri.nb4a.proxy.neko.NekoJSInterface -import moe.matsuri.nb4a.proxy.neko.NekoSettingActivity -import moe.matsuri.nb4a.proxy.neko.canShare import moe.matsuri.nb4a.proxy.shadowtls.ShadowTLSSettingsActivity import okhttp3.internal.closeQuietly import java.net.InetAddress import java.net.InetSocketAddress import java.net.Socket import java.net.UnknownHostException -import java.util.* +import java.util.Collections import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.atomic.AtomicInteger import java.util.zip.ZipInputStream @@ -403,38 +445,6 @@ class ConfigurationFragment @JvmOverloads constructor( startActivity(Intent(requireActivity(), ChainSettingsActivity::class.java)) } - R.id.action_new_neko -> { - val context = requireContext() - lateinit var dialog: AlertDialog - val linearLayout = LinearLayout(context).apply { - orientation = LinearLayout.VERTICAL - - NekoPluginManager.getProtocols().forEach { obj -> - LayoutAppsItemBinding.inflate(layoutInflater, this, true).apply { - itemcheck.isGone = true - button.isGone = false - itemicon.setImageDrawable( - PackageCache.installedApps[obj.plgId]?.loadIcon( - context.packageManager - ) - ) - title.text = obj.protocolId - desc.text = obj.plgId - button.setOnClickListener { - dialog.dismiss() - val intent = Intent(context, NekoSettingActivity::class.java) - intent.putExtra("plgId", obj.plgId) - intent.putExtra("protocolId", obj.protocolId) - startActivity(intent) - } - } - } - } - dialog = MaterialAlertDialogBuilder(context).setTitle(R.string.neko_plugin) - .setView(linearLayout) - .show() - } - R.id.action_update_subscription -> { val group = DataStore.currentGroup() if (group.type != GroupType.SUBSCRIPTION) { @@ -870,7 +880,6 @@ class ConfigurationFragment @JvmOverloads constructor( } } GroupManager.postReload(DataStore.currentGroupId()) - NekoJSInterface.Default.destroyAllJsi() mainJob.cancel() testJobs.forEach { it.cancel() } } @@ -1591,7 +1600,7 @@ class ConfigurationFragment @JvmOverloads constructor( removeButton.isGone = select proxyEntity.nekoBean?.apply { - shareLayout.isGone = !canShare() + shareLayout.isGone = true } runOnDefaultDispatcher { diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt index 4de4242..5c91718 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt @@ -16,13 +16,11 @@ import io.nekohasekai.sagernet.database.DataStore import io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers import io.nekohasekai.sagernet.ktx.* import io.nekohasekai.sagernet.utils.Theme -import io.nekohasekai.sagernet.widget.AppListPreference import moe.matsuri.nb4a.ui.* class SettingsPreferenceFragment : PreferenceFragmentCompat() { private lateinit var isProxyApps: SwitchPreference - private lateinit var nekoPlugins: AppListPreference override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -40,16 +38,6 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() { DataStore.initGlobal() addPreferencesFromResource(R.xml.global_preferences) - DataStore.routePackages = DataStore.nekoPlugins - nekoPlugins = findPreference(Key.NEKO_PLUGIN_MANAGED)!! - nekoPlugins.setOnPreferenceClickListener { - // borrow from route app settings - startActivity(Intent( - context, AppListActivity::class.java - ).apply { putExtra(Key.NEKO_PLUGIN_MANAGED, true) }) - true - } - val appTheme = findPreference(Key.APP_THEME)!! appTheme.setOnPreferenceChangeListener { _, newTheme -> if (DataStore.serviceState.started) { @@ -183,9 +171,6 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() { if (::isProxyApps.isInitialized) { isProxyApps.isChecked = DataStore.proxyApps } - if (::nekoPlugins.isInitialized) { - nekoPlugins.postUpdate() - } } } \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/utils/PackageCache.kt b/app/src/main/java/io/nekohasekai/sagernet/utils/PackageCache.kt index 2ffaef8..a2e09ef 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/utils/PackageCache.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/utils/PackageCache.kt @@ -51,7 +51,7 @@ object PackageCache { }.associateBy { it.packageName } installedPluginPackages = rawPackageInfo.filter { - Plugins.isExeOrPlugin(it) + Plugins.isExe(it) }.associateBy { it.packageName } val installed = app.packageManager.getInstalledApplications(PackageManager.GET_META_DATA) diff --git a/app/src/main/java/moe/matsuri/nb4a/plugin/NekoPluginManager.kt b/app/src/main/java/moe/matsuri/nb4a/plugin/NekoPluginManager.kt deleted file mode 100644 index d7ae50e..0000000 --- a/app/src/main/java/moe/matsuri/nb4a/plugin/NekoPluginManager.kt +++ /dev/null @@ -1,153 +0,0 @@ -package moe.matsuri.nb4a.plugin - -import io.nekohasekai.sagernet.R -import io.nekohasekai.sagernet.SagerNet -import io.nekohasekai.sagernet.bg.BaseService -import io.nekohasekai.sagernet.database.DataStore -import io.nekohasekai.sagernet.ktx.forEach -import io.nekohasekai.sagernet.utils.PackageCache -import moe.matsuri.nb4a.proxy.neko.NekoJSInterface -import okhttp3.internal.closeQuietly -import org.json.JSONObject -import java.io.File -import java.util.zip.CRC32 -import java.util.zip.ZipFile - -object NekoPluginManager { - const val managerVersion = 2 - - val plugins get() = DataStore.nekoPlugins.split("\n").filter { it.isNotBlank() } - - // plgID to plgConfig object - fun getManagedPlugins(): Map { - val ret = mutableMapOf() - plugins.forEach { - tryGetPlgConfig(it)?.apply { - ret[it] = this - } - } - return ret - } - - class Protocol( - val protocolId: String, val plgId: String, val protocolConfig: JSONObject - ) - - fun getProtocols(): List { - val ret = mutableListOf() - getManagedPlugins().forEach { (t, u) -> - u.optJSONArray("protocols")?.forEach { _, any -> - if (any is JSONObject) { - val name = any.optString("protocolId") - ret.add(Protocol(name, t, any)) - } - } - } - return ret - } - - fun findProtocol(protocolId: String): Protocol? { - getManagedPlugins().forEach { (t, u) -> - u.optJSONArray("protocols")?.forEach { _, any -> - if (any is JSONObject) { - if (protocolId == any.optString("protocolId")) { - return Protocol(protocolId, t, any) - } - } - } - } - return null - } - - fun removeManagedPlugin(plgId: String) { - DataStore.configurationStore.remove(plgId) - val dir = File(SagerNet.application.filesDir.absolutePath + "/plugins/" + plgId) - if (dir.exists()) { - dir.deleteRecursively() - } - } - - fun extractPlugin(plgId: String, install: Boolean) { - val app = PackageCache.installedApps[plgId] ?: return - val apk = File(app.publicSourceDir) - if (!apk.exists()) { - return - } - if (!install && !plugins.contains(plgId)) { - return - } - - val zipFile = ZipFile(apk) - val unzipDir = File(SagerNet.application.filesDir.absolutePath + "/plugins/" + plgId) - unzipDir.mkdirs() - for (entry in zipFile.entries()) { - if (entry.name.startsWith("assets/")) { - val relativePath = entry.name.removePrefix("assets/") - val outFile = File(unzipDir, relativePath) - if (entry.isDirectory) { - outFile.mkdirs() - continue - } - - if (outFile.isDirectory) { - outFile.delete() - } else if (outFile.exists()) { - val checksum = CRC32() - checksum.update(outFile.readBytes()) - if (checksum.value == entry.crc) { - continue - } - } - - val input = zipFile.getInputStream(entry) - outFile.outputStream().use { - input.copyTo(it) - } - } - } - zipFile.closeQuietly() - } - - suspend fun installPlugin(plgId: String) { - if (plgId == "moe.matsuri.plugin.singbox" || plgId == "moe.matsuri.plugin.xray") { - throw Exception("This plugin is deprecated") - } - extractPlugin(plgId, true) - NekoJSInterface.Default.destroyJsi(plgId) - NekoJSInterface.Default.requireJsi(plgId).init() - NekoJSInterface.Default.destroyJsi(plgId) - } - - const val PLUGIN_APP_VERSION = "_v_vc" - const val PLUGIN_APP_VERSION_NAME = "_v_vn" - - // Return null if not managed - fun tryGetPlgConfig(plgId: String): JSONObject? { - return try { - JSONObject(DataStore.configurationStore.getString(plgId)!!) - } catch (e: Exception) { - null - } - } - - fun updatePlgConfig(plgId: String, plgConfig: JSONObject) { - PackageCache.installedPluginPackages[plgId]?.apply { - // longVersionCode requires API 28 -// plgConfig.put(PLUGIN_APP_VERSION, versionCode) - plgConfig.put(PLUGIN_APP_VERSION_NAME, versionName) - } - DataStore.configurationStore.putString(plgId, plgConfig.toString()) - } - - fun htmlPath(plgId: String): String { - val htmlFile = File(SagerNet.application.filesDir.absolutePath + "/plugins/" + plgId) - return htmlFile.absolutePath - } - - class PluginInternalException(val protocolId: String) : Exception(), - BaseService.ExpectedException { - override fun getLocalizedMessage() = - SagerNet.application.getString(R.string.neko_plugin_internal_error, protocolId) - } - -} \ No newline at end of file diff --git a/app/src/main/java/moe/matsuri/nb4a/plugin/Plugins.kt b/app/src/main/java/moe/matsuri/nb4a/plugin/Plugins.kt index 43b80bb..a584cad 100644 --- a/app/src/main/java/moe/matsuri/nb4a/plugin/Plugins.kt +++ b/app/src/main/java/moe/matsuri/nb4a/plugin/Plugins.kt @@ -14,20 +14,18 @@ import io.nekohasekai.sagernet.utils.PackageCache object Plugins { const val AUTHORITIES_PREFIX_SEKAI_EXE = "io.nekohasekai.sagernet.plugin." const val AUTHORITIES_PREFIX_NEKO_EXE = "moe.matsuri.exe." - const val AUTHORITIES_PREFIX_NEKO_PLUGIN = "moe.matsuri.plugin." const val ACTION_NATIVE_PLUGIN = "io.nekohasekai.sagernet.plugin.ACTION_NATIVE_PLUGIN" const val METADATA_KEY_ID = "io.nekohasekai.sagernet.plugin.id" const val METADATA_KEY_EXECUTABLE_PATH = "io.nekohasekai.sagernet.plugin.executable_path" - fun isExeOrPlugin(pkg: PackageInfo): Boolean { + fun isExe(pkg: PackageInfo): Boolean { if (pkg.providers?.isEmpty() == true) return false val provider = pkg.providers?.get(0) ?: return false val auth = provider.authority ?: return false return auth.startsWith(AUTHORITIES_PREFIX_SEKAI_EXE) || auth.startsWith(AUTHORITIES_PREFIX_NEKO_EXE) - || auth.startsWith(AUTHORITIES_PREFIX_NEKO_PLUGIN) } fun preferExePrefix(): String { diff --git a/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoBean.java b/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoBean.java index 3a86b7f..434d549 100644 --- a/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoBean.java +++ b/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoBean.java @@ -8,18 +8,12 @@ import com.esotericsoftware.kryo.io.ByteBufferOutput; import org.jetbrains.annotations.NotNull; import org.json.JSONObject; -import io.nekohasekai.sagernet.R; -import io.nekohasekai.sagernet.SagerNet; import io.nekohasekai.sagernet.fmt.AbstractBean; import io.nekohasekai.sagernet.fmt.KryoConverters; import io.nekohasekai.sagernet.ktx.Logs; -import moe.matsuri.nb4a.plugin.NekoPluginManager; public class NekoBean extends AbstractBean { - // BoxInstance use this - public JSONObject allConfig = null; - public String plgId; public String protocolId; public JSONObject sharedStorage = new JSONObject(); @@ -62,31 +56,22 @@ public class NekoBean extends AbstractBean { } public String displayType() { - NekoPluginManager.Protocol p = NekoPluginManager.INSTANCE.findProtocol(protocolId); - String neko = SagerNet.application.getResources().getString(R.string.neko_plugin); - if (p == null) return neko; - return p.getProtocolId(); + return "invalid"; } @Override public boolean canMapping() { - NekoPluginManager.Protocol p = NekoPluginManager.INSTANCE.findProtocol(protocolId); - if (p == null) return false; - return p.getProtocolConfig().optBoolean("canMapping"); + return false; } @Override public boolean canICMPing() { - NekoPluginManager.Protocol p = NekoPluginManager.INSTANCE.findProtocol(protocolId); - if (p == null) return false; - return p.getProtocolConfig().optBoolean("canICMPing"); + return false; } @Override public boolean canTCPing() { - NekoPluginManager.Protocol p = NekoPluginManager.INSTANCE.findProtocol(protocolId); - if (p == null) return false; - return p.getProtocolConfig().optBoolean("canTCPing"); + return false; } @NotNull diff --git a/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoFmt.kt b/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoFmt.kt deleted file mode 100644 index eb29e53..0000000 --- a/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoFmt.kt +++ /dev/null @@ -1,123 +0,0 @@ -package moe.matsuri.nb4a.proxy.neko - -import io.nekohasekai.sagernet.database.DataStore -import io.nekohasekai.sagernet.ktx.Logs -import io.nekohasekai.sagernet.ktx.getStr -import io.nekohasekai.sagernet.ktx.runOnIoDispatcher -import libcore.Libcore -import moe.matsuri.nb4a.Protocols -import moe.matsuri.nb4a.plugin.NekoPluginManager -import org.json.JSONObject -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine - -suspend fun parseShareLink(plgId: String, protocolId: String, link: String): NekoBean = - suspendCoroutine { - runOnIoDispatcher { - val jsi = NekoJSInterface.Default.requireJsi(plgId) - jsi.lock() - - try { - jsi.init() - - val jsip = jsi.switchProtocol(protocolId) - val sharedStorage = jsip.parseShareLink(link) - - // NekoBean from link - val bean = NekoBean() - bean.plgId = plgId - bean.protocolId = protocolId - bean.sharedStorage = NekoBean.tryParseJSON(sharedStorage) - bean.onSharedStorageSet() - - it.resume(bean) - } catch (e: Exception) { - Logs.e(e) - it.resume(NekoBean().apply { - this.plgId = plgId - this.protocolId = protocolId - }) - } - - jsi.unlock() - // destroy when all link parsed - } - } - -fun NekoBean.shareLink(): String { - return sharedStorage.optString("shareLink") -} - -// Only run in bg process -// seems no concurrent -suspend fun NekoBean.updateAllConfig(port: Int) = suspendCoroutine { - allConfig = null - - runOnIoDispatcher { - val jsi = NekoJSInterface.Default.requireJsi(plgId) - jsi.lock() - - try { - jsi.init() - val jsip = jsi.switchProtocol(protocolId) - - // runtime arguments - val otherArgs = mutableMapOf() - otherArgs["finalAddress"] = finalAddress - otherArgs["finalPort"] = finalPort -// otherArgs["muxEnabled"] = Protocols.shouldEnableMux(protocolId) -// otherArgs["muxConcurrency"] = DataStore.muxConcurrency - - val ret = jsip.buildAllConfig(port, this@updateAllConfig, otherArgs) - - // result - allConfig = JSONObject(ret) - } catch (e: Exception) { - Logs.e(e) - } - - jsi.unlock() - it.resume(Unit) - // destroy when config generated / all tests finished - } -} - -fun NekoBean.cacheGet(id: String): String? { - return DataStore.profileCacheStore.getString("neko_${hash()}_$id") -} - -fun NekoBean.cacheSet(id: String, value: String) { - DataStore.profileCacheStore.putString("neko_${hash()}_$id", value) -} - -fun NekoBean.hash(): String { - var a = plgId - a += protocolId - a += sharedStorage.toString() - return Libcore.sha256Hex(a.toByteArray()) -} - -// must call it to update something like serverAddress -fun NekoBean.onSharedStorageSet() { - serverAddress = sharedStorage.getStr("serverAddress") - serverPort = sharedStorage.getStr("serverPort")?.toInt() ?: 1080 - if (serverAddress == null || serverAddress.isBlank()) { - serverAddress = "127.0.0.1" - } - name = sharedStorage.optString("name") -} - -fun NekoBean.needBypassRootUid(): Boolean { - val p = NekoPluginManager.findProtocol(protocolId) ?: return false - return p.protocolConfig.optBoolean("needBypassRootUid") -} - -fun NekoBean.haveStandardLink(): Boolean { - val p = NekoPluginManager.findProtocol(protocolId) ?: return false - return p.protocolConfig.optBoolean("haveStandardLink") -} - -fun NekoBean.canShare(): Boolean { - val p = NekoPluginManager.findProtocol(protocolId) ?: return false - return p.protocolConfig.optBoolean("canShare") -} diff --git a/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoJSInterface.kt b/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoJSInterface.kt deleted file mode 100644 index df165c7..0000000 --- a/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoJSInterface.kt +++ /dev/null @@ -1,388 +0,0 @@ -package moe.matsuri.nb4a.proxy.neko - -import android.annotation.SuppressLint -import android.webkit.* -import android.widget.Toast -import androidx.preference.Preference -import androidx.preference.PreferenceScreen -import io.nekohasekai.sagernet.BuildConfig -import io.nekohasekai.sagernet.SagerNet -import io.nekohasekai.sagernet.database.DataStore -import io.nekohasekai.sagernet.ktx.* -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.withContext -import moe.matsuri.nb4a.plugin.NekoPluginManager -import moe.matsuri.nb4a.ui.SimpleMenuPreference -import moe.matsuri.nb4a.utils.JavaUtil -import moe.matsuri.nb4a.utils.Util -import moe.matsuri.nb4a.utils.WebViewUtil -import org.json.JSONObject -import java.io.File -import java.io.FileInputStream -import java.util.* -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicBoolean -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine - -class NekoJSInterface(val plgId: String) { - - private val mutex = Mutex() - private var webView: WebView? = null - val jsObject = JsObject() - var plgConfig: JSONObject? = null - var plgConfigException: Exception? = null - val protocols = mutableMapOf() - val loaded = AtomicBoolean() - - suspend fun lock() { - mutex.lock(null) - } - - fun unlock() { - mutex.unlock(null) - } - - // load webview and js - // Return immediately when already loaded - // Return plgConfig or throw exception - suspend fun init() = withContext(Dispatchers.Main) { - initInternal() - } - - @SuppressLint("SetJavaScriptEnabled") - private suspend fun initInternal() = suspendCoroutine { - if (loaded.get()) { - plgConfig?.apply { - it.resume(this) - return@suspendCoroutine - } - plgConfigException?.apply { - it.resumeWithException(this) - return@suspendCoroutine - } - it.resumeWithException(Exception("wtf")) - return@suspendCoroutine - } - - WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG) - NekoPluginManager.extractPlugin(plgId, false) - - webView = WebView(SagerNet.application.applicationContext) - webView!!.settings.javaScriptEnabled = true - webView!!.addJavascriptInterface(jsObject, "neko") - webView!!.webViewClient = object : WebViewClient() { - // provide files - override fun shouldInterceptRequest( - view: WebView?, request: WebResourceRequest? - ): WebResourceResponse { - return WebViewUtil.interceptRequest( - { res -> - val f = File(NekoPluginManager.htmlPath(plgId), res) - if (f.exists()) { - FileInputStream(f) - } else { - null - } - }, - view, - request - ) - } - - override fun onReceivedError( - view: WebView?, request: WebResourceRequest?, error: WebResourceError? - ) { - WebViewUtil.onReceivedError(view, request, error) - } - - override fun onPageFinished(view: WebView?, url: String?) { - super.onPageFinished(view, url) - if (loaded.getAndSet(true)) return - - runOnIoDispatcher { - // Process nekoInit - var ret = "" - try { - ret = nekoInit() - val obj = JSONObject(ret) - if (!obj.getBoolean("ok")) { - throw Exception("plugin refuse to run: ${obj.optString("reason")}") - } - val min = obj.getInt("minVersion") - if (min > NekoPluginManager.managerVersion) { - throw Exception("manager version ${NekoPluginManager.managerVersion} too old, this plugin requires >= $min") - } - plgConfig = obj - NekoPluginManager.updatePlgConfig(plgId, obj) - it.resume(obj) - } catch (e: Exception) { - val e2 = Exception("nekoInit: " + e.readableMessage + "\n\n" + ret) - plgConfigException = e2 - it.resumeWithException(e2) - } - } - } - } - webView!!.loadUrl("http://$plgId/plugin.html") - } - - // Android call JS - - private suspend fun callJS(script: String): String = suspendCoroutine { - val jsLatch = CountDownLatch(1) - var jsReceivedValue = "" - - runOnMainDispatcher { - if (webView != null) { - webView!!.evaluateJavascript(script) { value -> - jsReceivedValue = value - jsLatch.countDown() - } - } else { - jsReceivedValue = "webView is null" - jsLatch.countDown() - } - } - - jsLatch.await(5, TimeUnit.SECONDS) - - // evaluateJavascript escapes Javascript's String - jsReceivedValue = JavaUtil.unescapeString(jsReceivedValue.removeSurrounding("\"")) - if (BuildConfig.DEBUG) Logs.d("$script: $jsReceivedValue") - it.resume(jsReceivedValue) - } - - // call once - private suspend fun nekoInit(): String { - val sendData = JSONObject() - sendData.put("lang", Locale.getDefault().toLanguageTag()) - sendData.put("plgId", plgId) - sendData.put("managerVersion", NekoPluginManager.managerVersion) - - return callJS( - "nekoInit(\"${ - Util.b64EncodeUrlSafe( - sendData.toString().toByteArray() - ) - }\")" - ) - } - - fun switchProtocol(id: String): NekoProtocol { - lateinit var p: NekoProtocol - if (protocols.containsKey(id)) { - p = protocols[id]!! - } else { - p = NekoProtocol(id) { callJS(it) } - protocols[id] = p - } - jsObject.protocol = p - return p - } - - suspend fun getAbout(): String { - return callJS("nekoAbout()") - } - - inner class NekoProtocol(val protocolId: String, val callJS: suspend (String) -> String) { - private suspend fun callProtocol(method: String, b64Str: String?): String { - var arg = "" - if (b64Str != null) { - arg = "\"" + b64Str + "\"" - } - return callJS("nekoProtocol(\"$protocolId\").$method($arg)") - } - - suspend fun buildAllConfig( - port: Int, bean: NekoBean, otherArgs: Map? - ): String { - val sendData = JSONObject() - sendData.put("port", port) - sendData.put( - "sharedStorage", - Util.b64EncodeUrlSafe(bean.sharedStorage.toString().toByteArray()) - ) - otherArgs?.forEach { (t, u) -> sendData.put(t, u) } - - return callProtocol( - "buildAllConfig", Util.b64EncodeUrlSafe(sendData.toString().toByteArray()) - ) - } - - suspend fun parseShareLink(shareLink: String): String { - val sendData = JSONObject() - sendData.put("shareLink", shareLink) - - return callProtocol( - "parseShareLink", Util.b64EncodeUrlSafe(sendData.toString().toByteArray()) - ) - } - - // UI Interface - - suspend fun setSharedStorage(sharedStorage: String) { - callProtocol( - "setSharedStorage", - Util.b64EncodeUrlSafe(sharedStorage.toByteArray()) - ) - } - - suspend fun requireSetProfileCache() { - callProtocol("requireSetProfileCache", null) - } - - suspend fun requirePreferenceScreenConfig(): String { - return callProtocol("requirePreferenceScreenConfig", null) - } - - suspend fun sharedStorageFromProfileCache(): String { - return callProtocol("sharedStorageFromProfileCache", null) - } - - suspend fun onPreferenceCreated() { - callProtocol("onPreferenceCreated", null) - } - - suspend fun onPreferenceChanged(key: String, v: Any) { - val sendData = JSONObject() - sendData.put("key", key) - sendData.put("newValue", v) - - callProtocol( - "onPreferenceChanged", - Util.b64EncodeUrlSafe(sendData.toString().toByteArray()) - ) - } - - } - - inner class JsObject { - var preferenceScreen: PreferenceScreen? = null - var protocol: NekoProtocol? = null - - // JS call Android - - @JavascriptInterface - fun toast(s: String) { - Toast.makeText(SagerNet.application.applicationContext, s, Toast.LENGTH_SHORT).show() - } - - @JavascriptInterface - fun logError(s: String) { - Logs.e("logError: $s") - } - - @JavascriptInterface - fun setPreferenceVisibility(key: String, isVisible: Boolean) { - runBlockingOnMainDispatcher { - preferenceScreen?.findPreference(key)?.isVisible = isVisible - } - } - - @JavascriptInterface - fun setPreferenceTitle(key: String, title: String) { - runBlockingOnMainDispatcher { - preferenceScreen?.findPreference(key)?.title = title - } - } - - @JavascriptInterface - fun setMenu(key: String, entries: String) { - runBlockingOnMainDispatcher { - preferenceScreen?.findPreference(key)?.apply { - NekoPreferenceInflater.setMenu(this, JSONObject(entries)) - } - } - } - - @JavascriptInterface - fun listenOnPreferenceChanged(key: String) { - preferenceScreen?.findPreference(key) - ?.setOnPreferenceChangeListener { preference, newValue -> - runOnIoDispatcher { - protocol?.onPreferenceChanged(preference.key, newValue) - } - true - } - } - - @JavascriptInterface - fun setKV(type: Int, key: String, jsonStr: String) { - try { - val v = JSONObject(jsonStr) - when (type) { - 0 -> DataStore.profileCacheStore.putBoolean(key, v.getBoolean("v")) - 1 -> DataStore.profileCacheStore.putFloat(key, v.getDouble("v").toFloat()) - 2 -> DataStore.profileCacheStore.putInt(key, v.getInt("v")) - 3 -> DataStore.profileCacheStore.putLong(key, v.getLong("v")) - 4 -> DataStore.profileCacheStore.putString(key, v.getString("v")) - } - } catch (e: Exception) { - Logs.e("setKV: $e") - } - } - - @JavascriptInterface - fun getKV(type: Int, key: String): String { - val v = JSONObject() - try { - when (type) { - 0 -> v.put("v", DataStore.profileCacheStore.getBoolean(key)) - 1 -> v.put("v", DataStore.profileCacheStore.getFloat(key)) - 2 -> v.put("v", DataStore.profileCacheStore.getInt(key)) - 3 -> v.put("v", DataStore.profileCacheStore.getLong(key)) - 4 -> v.put("v", DataStore.profileCacheStore.getString(key)) - } - } catch (e: Exception) { - Logs.e("getKV: $e") - } - return v.toString() - } - - } - - fun destroy() { - webView?.onPause() - webView?.removeAllViews() - webView?.destroy() - webView = null - } - - suspend fun destorySuspend() = withContext(Dispatchers.Main) { - destroy() - } - - object Default { - val map = mutableMapOf() - - suspend fun destroyJsi(plgId: String) = withContext(Dispatchers.Main) { - if (map.containsKey(plgId)) { - map[plgId]!!.destroy() - map.remove(plgId) - } - } - - // now it's manually managed - suspend fun destroyAllJsi() = withContext(Dispatchers.Main) { - map.forEach { (t, u) -> - u.destroy() - map.remove(t) - } - } - - suspend fun requireJsi(plgId: String): NekoJSInterface = withContext(Dispatchers.Main) { - lateinit var jsi: NekoJSInterface - if (map.containsKey(plgId)) { - jsi = map[plgId]!! - } else { - jsi = NekoJSInterface(plgId) - map[plgId] = jsi - } - return@withContext jsi - } - } -} diff --git a/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoPreferenceInflater.kt b/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoPreferenceInflater.kt deleted file mode 100644 index cd2fdff..0000000 --- a/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoPreferenceInflater.kt +++ /dev/null @@ -1,97 +0,0 @@ -package moe.matsuri.nb4a.proxy.neko - -import androidx.preference.* -import io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers -import io.nekohasekai.sagernet.ktx.forEach -import io.nekohasekai.sagernet.ktx.getStr -import io.nekohasekai.sagernet.ui.profile.ProfileSettingsActivity -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import moe.matsuri.nb4a.ui.SimpleMenuPreference -import moe.matsuri.nb4a.utils.getDrawableByName -import org.json.JSONArray -import org.json.JSONObject - -object NekoPreferenceInflater { - suspend fun inflate(pref: JSONArray, preferenceScreen: PreferenceScreen) = - withContext(Dispatchers.Main) { - val context = preferenceScreen.context - pref.forEach { _, category -> - category as JSONObject - - val preferenceCategory = PreferenceCategory(context) - preferenceScreen.addPreference(preferenceCategory) - - category.getStr("key")?.apply { preferenceCategory.key = this } - category.getStr("title")?.apply { preferenceCategory.title = this } - - category.optJSONArray("preferences")?.forEach { _, any -> - if (any is JSONObject) { - lateinit var p: Preference - // Create Preference - when (any.getStr("type")) { - "EditTextPreference" -> { - p = EditTextPreference(context).apply { - when (any.getStr("summaryProvider")) { - null -> summaryProvider = - EditTextPreference.SimpleSummaryProvider.getInstance() - "PasswordSummaryProvider" -> summaryProvider = - ProfileSettingsActivity.PasswordSummaryProvider - } - when (any.getStr("EditTextPreferenceModifiers")) { - "Monospace" -> setOnBindEditTextListener( - EditTextPreferenceModifiers.Monospace - ) - "Hosts" -> setOnBindEditTextListener( - EditTextPreferenceModifiers.Hosts - ) - "Port" -> setOnBindEditTextListener( - EditTextPreferenceModifiers.Port - ) - "Number" -> setOnBindEditTextListener( - EditTextPreferenceModifiers.Number - ) - } - } - } - "SwitchPreference" -> { - p = SwitchPreference(context) - } - "SimpleMenuPreference" -> { - p = SimpleMenuPreference(context).apply { - val entries = any.optJSONObject("entries") - if (entries != null) setMenu(this, entries) - } - } - } - // Set key & title - p.key = any.getString("key") - any.getStr("title")?.apply { p.title = this } - // Set icon - any.getStr("icon")?.apply { - p.icon = context.getDrawableByName(this) - } - // Set summary - any.getStr("summary")?.apply { - p.summary = this - } - // Add to category - preferenceCategory.addPreference(p) - } - } - } - } - - fun setMenu(p: SimpleMenuPreference, entries: JSONObject) { - val menuEntries = mutableListOf() - val menuEntryValues = mutableListOf() - entries.forEach { s, b -> - menuEntryValues.add(s) - menuEntries.add(b as String) - } - entries.apply { - p.entries = menuEntries.toTypedArray() - p.entryValues = menuEntryValues.toTypedArray() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoSettingActivity.kt b/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoSettingActivity.kt deleted file mode 100644 index e167fd6..0000000 --- a/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoSettingActivity.kt +++ /dev/null @@ -1,102 +0,0 @@ -package moe.matsuri.nb4a.proxy.neko - -import android.os.Bundle -import android.view.View -import androidx.core.view.isVisible -import androidx.preference.PreferenceDataStore -import androidx.preference.PreferenceFragmentCompat -import io.nekohasekai.sagernet.Key -import io.nekohasekai.sagernet.R -import io.nekohasekai.sagernet.database.DataStore -import io.nekohasekai.sagernet.ktx.runOnIoDispatcher -import io.nekohasekai.sagernet.ui.profile.ProfileSettingsActivity -import moe.matsuri.nb4a.ui.Dialogs -import org.json.JSONArray - -class NekoSettingActivity : ProfileSettingsActivity() { - - lateinit var jsi: NekoJSInterface - lateinit var jsip: NekoJSInterface.NekoProtocol - lateinit var plgId: String - lateinit var protocolId: String - var loaded = false - - override fun createEntity() = NekoBean() - - override fun NekoBean.init() { - if (!this@NekoSettingActivity::plgId.isInitialized) this@NekoSettingActivity.plgId = plgId - if (!this@NekoSettingActivity::protocolId.isInitialized) this@NekoSettingActivity.protocolId = protocolId - DataStore.profileCacheStore.putString("name", name) - DataStore.sharedStorage = sharedStorage.toString() - } - - override fun NekoBean.serialize() { - // NekoBean from input - plgId = this@NekoSettingActivity.plgId - protocolId = this@NekoSettingActivity.protocolId - - sharedStorage = NekoBean.tryParseJSON(DataStore.sharedStorage) - onSharedStorageSet() - } - - override fun onCreate(savedInstanceState: Bundle?) { - intent?.getStringExtra("plgId")?.apply { plgId = this } - intent?.getStringExtra("protocolId")?.apply { protocolId = this } - super.onCreate(savedInstanceState) - } - - override fun PreferenceFragmentCompat.viewCreated(view: View, savedInstanceState: Bundle?) { - listView.isVisible = false - } - - override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) { - if (loaded && key != Key.PROFILE_DIRTY) { - DataStore.dirty = true - } - } - - override fun PreferenceFragmentCompat.createPreferences( - savedInstanceState: Bundle?, - rootKey: String?, - ) { - addPreferencesFromResource(R.xml.neko_preferences) - - // Create a jsi - jsi = NekoJSInterface(plgId) - runOnIoDispatcher { - try { - jsi.init() - jsip = jsi.switchProtocol(protocolId) - jsi.jsObject.preferenceScreen = preferenceScreen - - // Because of the Preference problem, first require the KV and then inflate the UI - jsip.setSharedStorage(DataStore.sharedStorage) - jsip.requireSetProfileCache() - - val config = jsip.requirePreferenceScreenConfig() - val pref = JSONArray(config) - - NekoPreferenceInflater.inflate(pref, preferenceScreen) - jsip.onPreferenceCreated() - - runOnUiThread { - loaded = true - listView.isVisible = true - } - } catch (e: Exception) { - Dialogs.logExceptionAndShow(this@NekoSettingActivity, e) { finish() } - } - } - } - - override suspend fun saveAndExit() { - DataStore.sharedStorage = jsip.sharedStorageFromProfileCache() - super.saveAndExit() // serialize & finish - } - - override fun onDestroy() { - jsi.destroy() - super.onDestroy() - } - -} \ No newline at end of file diff --git a/app/src/main/res/layout/layout_app_list.xml b/app/src/main/res/layout/layout_app_list.xml index f013d4d..45432fa 100644 --- a/app/src/main/res/layout/layout_app_list.xml +++ b/app/src/main/res/layout/layout_app_list.xml @@ -82,61 +82,6 @@ - - - - - - - - - - - - - - -