diff --git a/build.gradle b/build.gradle
index ccc6a36..9b82d35 100644
--- a/build.gradle
+++ b/build.gradle
@@ -14,8 +14,8 @@ buildscript {
junitVersion = '4.13'
androidTestVersion = '1.2.0'
androidEspressoVersion = '3.2.0'
- versionCode = 5000968
- versionName = '5.1.21-nightly'
+ versionCode = 5001008
+ versionName = '5.1.24-nightly'
resConfigs = ['ar', 'es', 'fa', 'fr', 'ja', 'ko', 'ru', 'tr', 'zh-rCN', 'zh-rTW']
}
diff --git a/gitupdate.bat b/gitupdate.bat
index 2e7030a..20a5de0 100644
--- a/gitupdate.bat
+++ b/gitupdate.bat
@@ -3,6 +3,6 @@ git pull origin master
git add -A
git commit -m "update"
git push origin master
-git tag -a v5.1.22 -m "release v5.1.22"
+git tag -a v5.1.24 -m "release v5.1.24"
git push origin --tags
pause
\ No newline at end of file
diff --git a/libv2ray/libv2ray.aar b/libv2ray/libv2ray.aar
deleted file mode 100644
index 79b35c3..0000000
Binary files a/libv2ray/libv2ray.aar and /dev/null differ
diff --git a/tv/.gitignore b/tv/.gitignore
deleted file mode 100644
index 796b96d..0000000
--- a/tv/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
diff --git a/tv/build.gradle b/tv/build.gradle
deleted file mode 100644
index 1453c67..0000000
--- a/tv/build.gradle
+++ /dev/null
@@ -1,71 +0,0 @@
-import com.android.build.OutputFile
-import java.util.regex.Matcher
-import java.util.regex.Pattern
-
-apply plugin: 'com.android.application'
-apply plugin: 'com.google.android.gms.oss-licenses-plugin'
-apply plugin: 'com.google.firebase.crashlytics'
-apply plugin: 'com.google.gms.google-services'
-apply plugin: 'kotlin-android'
-apply plugin: 'kotlin-kapt'
-
-def getCurrentFlavor() {
- String task = getGradle().getStartParameter().getTaskRequests().toString()
- Matcher matcher = Pattern.compile("(assemble|generate)\\w*(Release|Debug)").matcher(task)
- if (matcher.find()) return matcher.group(2).toLowerCase() else {
- println "Warning: No match found for $task"
- return "debug"
- }
-}
-
-android {
- compileSdkVersion rootProject.compileSdkVersion
- defaultConfig {
- applicationId "com.github.shadowsocks.tv"
- minSdkVersion rootProject.minSdkVersion
- targetSdkVersion rootProject.sdkVersion
- versionCode rootProject.versionCode
- versionName rootProject.versionName
- resConfigs rootProject.resConfigs
- }
- buildTypes {
- debug {
- pseudoLocalesEnabled true
- }
- release {
- shrinkResources true
- minifyEnabled true
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
- }
- compileOptions {
- coreLibraryDesugaringEnabled true
- sourceCompatibility javaVersion
- targetCompatibility javaVersion
- }
- kotlinOptions.jvmTarget = javaVersion
- packagingOptions.exclude '**/*.kotlin_*'
- splits {
- abi {
- enable true
- universalApk true
- }
- }
- sourceSets.main.jniLibs.srcDirs +=
- new File(project(':core').buildDir, "intermediates/bundles/${getCurrentFlavor()}/jni")
-}
-
-dependencies {
- coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$desugarLibsVersion"
- implementation project(':core')
- implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation 'androidx.leanback:leanback-preference:1.1.0-alpha03'
-}
-
-ext.abiCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, x86: 3, x86_64: 4]
-if (getCurrentFlavor() == 'release') android.applicationVariants.all { variant ->
- variant.outputs.each { output ->
- def offset = project.ext.abiCodes.get(output.getFilter(OutputFile.ABI))
- if (offset != null) output.versionCodeOverride = variant.versionCode + offset
- }
-}
diff --git a/tv/google-services.json b/tv/google-services.json
deleted file mode 100644
index 8c8c768..0000000
--- a/tv/google-services.json
+++ /dev/null
@@ -1,82 +0,0 @@
-{
- "project_info": {
- "project_number": "261400168171",
- "firebase_url": "https://admob-app-id-3330146721.firebaseio.com",
- "project_id": "admob-app-id-3330146721",
- "storage_bucket": "admob-app-id-3330146721.appspot.com"
- },
- "client": [
- {
- "client_info": {
- "mobilesdk_app_id": "1:261400168171:android:dbdd6331c434162f",
- "android_client_info": {
- "package_name": "com.github.shadowsocks"
- }
- },
- "oauth_client": [
- {
- "client_id": "261400168171-sfik8o3pj7e243583olorh7s5974vab1.apps.googleusercontent.com",
- "client_type": 1,
- "android_info": {
- "package_name": "com.github.shadowsocks",
- "certificate_hash": "58a90f84cfe99d4280aec677c9a1292fae131677"
- }
- },
- {
- "client_id": "261400168171-g7aelv5bu012ojr7dod7lq09c9anjimh.apps.googleusercontent.com",
- "client_type": 3
- }
- ],
- "api_key": [
- {
- "current_key": "AIzaSyCee3fAad7nb3YsxeUO9mqqHFfAvsSCbVs"
- }
- ],
- "services": {
- "analytics_service": {
- "status": 1
- },
- "appinvite_service": {
- "status": 1,
- "other_platform_oauth_client": []
- },
- "ads_service": {
- "status": 2
- }
- },
- "admob_app_id": "ca-app-pub-9097031975646651~3330146721"
- },
- {
- "client_info": {
- "mobilesdk_app_id": "1:261400168171:android:0dbac07695d93817",
- "android_client_info": {
- "package_name": "com.github.shadowsocks.tv"
- }
- },
- "oauth_client": [
- {
- "client_id": "261400168171-g7aelv5bu012ojr7dod7lq09c9anjimh.apps.googleusercontent.com",
- "client_type": 3
- }
- ],
- "api_key": [
- {
- "current_key": "AIzaSyCee3fAad7nb3YsxeUO9mqqHFfAvsSCbVs"
- }
- ],
- "services": {
- "analytics_service": {
- "status": 1
- },
- "appinvite_service": {
- "status": 1,
- "other_platform_oauth_client": []
- },
- "ads_service": {
- "status": 2
- }
- }
- }
- ],
- "configuration_version": "1"
-}
\ No newline at end of file
diff --git a/tv/src/main/AndroidManifest.xml b/tv/src/main/AndroidManifest.xml
deleted file mode 100644
index 45d8c5f..0000000
--- a/tv/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/tv/src/main/java/com/github/shadowsocks/tv/App.kt b/tv/src/main/java/com/github/shadowsocks/tv/App.kt
deleted file mode 100644
index 10d42e8..0000000
--- a/tv/src/main/java/com/github/shadowsocks/tv/App.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*******************************************************************************
- * *
- * Copyright (C) 2018 by Max Lv *
- * Copyright (C) 2018 by Mygod Studio *
- * *
- * This program is free software: you can redistribute it and/or modify *
- * it under the terms of the GNU General Public License as published by *
- * the Free Software Foundation, either version 3 of the License, or *
- * (at your option) any later version. *
- * *
- * This program is distributed in the hope that it will be useful, *
- * but WITHOUT ANY WARRANTY; without even the implied warranty of *
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
- * GNU General Public License for more details. *
- * *
- * You should have received a copy of the GNU General Public License *
- * along with this program. If not, see . *
- * *
- *******************************************************************************/
-
-package com.github.shadowsocks.tv
-
-import android.app.Application
-import android.content.res.Configuration
-import com.github.shadowsocks.Core
-
-class App : Application() {
- override fun onCreate() {
- super.onCreate()
- Core.init(this, MainActivity::class)
- }
-
- override fun onConfigurationChanged(newConfig: Configuration) {
- super.onConfigurationChanged(newConfig)
- Core.updateNotificationChannels()
- }
-}
diff --git a/tv/src/main/java/com/github/shadowsocks/tv/MainActivity.kt b/tv/src/main/java/com/github/shadowsocks/tv/MainActivity.kt
deleted file mode 100644
index 196dc0b..0000000
--- a/tv/src/main/java/com/github/shadowsocks/tv/MainActivity.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*******************************************************************************
- * *
- * Copyright (C) 2018 by Max Lv *
- * Copyright (C) 2018 by Mygod Studio *
- * *
- * This program is free software: you can redistribute it and/or modify *
- * it under the terms of the GNU General Public License as published by *
- * the Free Software Foundation, either version 3 of the License, or *
- * (at your option) any later version. *
- * *
- * This program is distributed in the hope that it will be useful, *
- * but WITHOUT ANY WARRANTY; without even the implied warranty of *
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
- * GNU General Public License for more details. *
- * *
- * You should have received a copy of the GNU General Public License *
- * along with this program. If not, see . *
- * *
- *******************************************************************************/
-
-package com.github.shadowsocks.tv
-
-import android.os.Bundle
-import androidx.fragment.app.FragmentActivity
-import com.github.shadowsocks.utils.SingleInstanceActivity
-
-class MainActivity : FragmentActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- SingleInstanceActivity.register(this) ?: return
- setContentView(R.layout.activity_main)
- }
-}
diff --git a/tv/src/main/java/com/github/shadowsocks/tv/MainFragment.kt b/tv/src/main/java/com/github/shadowsocks/tv/MainFragment.kt
deleted file mode 100644
index df10c72..0000000
--- a/tv/src/main/java/com/github/shadowsocks/tv/MainFragment.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*******************************************************************************
- * *
- * Copyright (C) 2018 by Max Lv *
- * Copyright (C) 2018 by Mygod Studio *
- * *
- * This program is free software: you can redistribute it and/or modify *
- * it under the terms of the GNU General Public License as published by *
- * the Free Software Foundation, either version 3 of the License, or *
- * (at your option) any later version. *
- * *
- * This program is distributed in the hope that it will be useful, *
- * but WITHOUT ANY WARRANTY; without even the implied warranty of *
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
- * GNU General Public License for more details. *
- * *
- * You should have received a copy of the GNU General Public License *
- * along with this program. If not, see . *
- * *
- *******************************************************************************/
-
-package com.github.shadowsocks.tv
-
-import android.os.Bundle
-import android.view.View
-import android.view.ViewGroup
-import androidx.core.os.bundleOf
-import androidx.core.view.updateLayoutParams
-import androidx.leanback.preference.LeanbackPreferenceDialogFragmentCompat
-import androidx.leanback.preference.LeanbackSettingsFragmentCompat
-import androidx.preference.*
-import com.github.shadowsocks.bg.BaseService
-import com.github.shadowsocks.tv.preference.LeanbackSingleListPreferenceDialogFragment
-import com.github.shadowsocks.utils.Key
-
-class MainFragment : LeanbackSettingsFragmentCompat() {
- override fun onPreferenceStartInitialScreen() = startPreferenceFragment(MainPreferenceFragment())
- override fun onPreferenceStartScreen(caller: PreferenceFragmentCompat?, pref: PreferenceScreen?): Boolean {
- onPreferenceStartInitialScreen()
- return true
- }
- override fun onPreferenceStartFragment(caller: PreferenceFragmentCompat?, pref: Preference?) = false
- override fun onPreferenceDisplayDialog(caller: PreferenceFragmentCompat, pref: Preference?): Boolean {
- if (pref?.key == Key.id) {
- if ((childFragmentManager.findFragmentById(R.id.settings_preference_fragment_container)
- as MainPreferenceFragment).state == BaseService.State.Stopped) {
- startPreferenceFragment(ProfilesDialogFragment().apply {
- arguments = bundleOf(Pair(LeanbackPreferenceDialogFragmentCompat.ARG_KEY, Key.id))
- setTargetFragment(caller, 0)
- })
- }
- return true
- }
- if (pref is ListPreference && pref !is MultiSelectListPreference) {
- startPreferenceFragment(LeanbackSingleListPreferenceDialogFragment().apply {
- arguments = bundleOf(Pair(LeanbackPreferenceDialogFragmentCompat.ARG_KEY, pref.key))
- setTargetFragment(caller, 0)
- })
- return true
- }
- return super.onPreferenceDisplayDialog(caller, pref)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- view.findViewById(R.id.settings_preference_fragment_container).updateLayoutParams {
- width = ViewGroup.LayoutParams.MATCH_PARENT
- }
- }
-}
diff --git a/tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt b/tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt
deleted file mode 100644
index d2b990a..0000000
--- a/tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt
+++ /dev/null
@@ -1,345 +0,0 @@
-/*******************************************************************************
- * *
- * Copyright (C) 2018 by Max Lv *
- * Copyright (C) 2018 by Mygod Studio *
- * *
- * This program is free software: you can redistribute it and/or modify *
- * it under the terms of the GNU General Public License as published by *
- * the Free Software Foundation, either version 3 of the License, or *
- * (at your option) any later version. *
- * *
- * This program is distributed in the hope that it will be useful, *
- * but WITHOUT ANY WARRANTY; without even the implied warranty of *
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
- * GNU General Public License for more details. *
- * *
- * You should have received a copy of the GNU General Public License *
- * along with this program. If not, see . *
- * *
- *******************************************************************************/
-
-package com.github.shadowsocks.tv
-
-import android.app.Activity
-import android.app.backup.BackupManager
-import android.content.ActivityNotFoundException
-import android.content.Intent
-import android.net.VpnService
-import android.os.Bundle
-import android.os.Handler
-import android.os.RemoteException
-import android.text.format.Formatter
-import android.util.Log
-import android.widget.Toast
-import androidx.fragment.app.viewModels
-import androidx.leanback.preference.LeanbackPreferenceFragmentCompat
-import androidx.lifecycle.observe
-import androidx.preference.*
-import com.crashlytics.android.Crashlytics
-import com.github.shadowsocks.BootReceiver
-import com.github.shadowsocks.Core
-import com.github.shadowsocks.aidl.IShadowsocksService
-import com.github.shadowsocks.aidl.ShadowsocksConnection
-import com.github.shadowsocks.aidl.TrafficStats
-import com.github.shadowsocks.bg.BaseService
-import com.github.shadowsocks.database.ProfileManager
-import com.github.shadowsocks.net.HttpsTest
-import com.github.shadowsocks.net.TcpFastOpen
-import com.github.shadowsocks.preference.DataStore
-import com.github.shadowsocks.preference.EditTextPreferenceModifiers
-import com.github.shadowsocks.preference.HostsSummaryProvider
-import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener
-import com.github.shadowsocks.utils.Key
-import com.github.shadowsocks.utils.datas
-import com.github.shadowsocks.utils.printLog
-import com.github.shadowsocks.utils.readableMessage
-import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
-
-class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksConnection.Callback,
- OnPreferenceDataStoreChangeListener {
- companion object {
- private const val REQUEST_CONNECT = 1
- private const val REQUEST_REPLACE_PROFILES = 2
- private const val REQUEST_EXPORT_PROFILES = 3
- private const val REQUEST_HOSTS = 4
- private const val TAG = "MainPreferenceFragment"
- }
-
- private lateinit var fab: ListPreference
- private lateinit var stats: Preference
- private lateinit var controlImport: Preference
- private lateinit var hosts: EditTextPreference
- private lateinit var serviceMode: Preference
- private lateinit var tfo: SwitchPreference
- private lateinit var shareOverLan: Preference
- private lateinit var portProxy: EditTextPreference
- private lateinit var portLocalDns: EditTextPreference
- private lateinit var portTransproxy: EditTextPreference
- private val onServiceModeChange = Preference.OnPreferenceChangeListener { _, newValue ->
- val (enabledLocalDns, enabledTransproxy) = when (newValue as String?) {
- Key.modeProxy -> Pair(false, false)
- Key.modeVpn -> Pair(true, false)
- Key.modeTransproxy -> Pair(true, true)
- else -> throw IllegalArgumentException("newValue: $newValue")
- }
- hosts.isEnabled = enabledLocalDns
- portLocalDns.isEnabled = enabledLocalDns
- portTransproxy.isEnabled = enabledTransproxy
- true
- }
- private val tester by viewModels()
-
- // service
- var state = BaseService.State.Idle
- private set
- override fun stateChanged(state: BaseService.State, profileName: String?, msg: String?) = changeState(state, msg)
- override fun trafficUpdated(profileId: Long, stats: TrafficStats) {
- if (profileId == 0L) context?.let { context ->
- this.stats.summary = getString(R.string.stat_summary,
- getString(R.string.speed, Formatter.formatFileSize(context, stats.txRate)),
- getString(R.string.speed, Formatter.formatFileSize(context, stats.rxRate)),
- Formatter.formatFileSize(context, stats.txTotal),
- Formatter.formatFileSize(context, stats.rxTotal))
- }
- }
-
- private fun changeState(state: BaseService.State, msg: String? = null) {
- val context = context ?: return
- fab.isEnabled = state.canStop || state == BaseService.State.Stopped
- fab.setTitle(when (state) {
- BaseService.State.Connecting -> R.string.connecting
- BaseService.State.Connected -> R.string.stop
- BaseService.State.Stopping -> R.string.stopping
- else -> R.string.connect
- })
- stats.setTitle(R.string.connection_test_pending)
- if ((state == BaseService.State.Connected).also { stats.isVisible = it }) tester.status.observe(this) {
- it.retrieve(stats::setTitle) { msg -> Toast.makeText(context, msg, Toast.LENGTH_LONG).show() }
- } else {
- trafficUpdated(0, TrafficStats())
- tester.status.removeObservers(this)
- if (state != BaseService.State.Idle) tester.invalidate()
- }
- if (msg != null) Toast.makeText(context, getString(R.string.vpn_error, msg), Toast.LENGTH_SHORT).show()
- this.state = state
- val stopped = state == BaseService.State.Stopped
- controlImport.isEnabled = stopped
- tfo.isEnabled = stopped
- serviceMode.isEnabled = stopped
- shareOverLan.isEnabled = stopped
- portProxy.isEnabled = stopped
- if (stopped) onServiceModeChange.onPreferenceChange(null, DataStore.serviceMode) else {
- portLocalDns.isEnabled = false
- portTransproxy.isEnabled = false
- }
- }
-
- private val handler = Handler()
- private val connection = ShadowsocksConnection(handler, true)
- override fun onServiceConnected(service: IShadowsocksService) = changeState(try {
- BaseService.State.values()[service.state]
- } catch (_: RemoteException) {
- BaseService.State.Idle
- })
- override fun onServiceDisconnected() = changeState(BaseService.State.Idle)
- override fun onBinderDied() {
- connection.disconnect(requireContext())
- connection.connect(requireContext(), this)
- }
-
- override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
- preferenceManager.preferenceDataStore = DataStore.publicStore
- DataStore.initGlobal()
- addPreferencesFromResource(R.xml.pref_main)
- fab = findPreference(Key.id)!!
- populateProfiles()
- stats = findPreference(Key.controlStats)!!
- controlImport = findPreference(Key.controlImport)!!
-
- findPreference(Key.persistAcrossReboot)!!.setOnPreferenceChangeListener { _, value ->
- BootReceiver.enabled = value as Boolean
- true
- }
-
- tfo = findPreference(Key.tfo)!!
- tfo.isChecked = DataStore.tcpFastOpen
- tfo.setOnPreferenceChangeListener { _, value ->
- if (value as Boolean && !TcpFastOpen.sendEnabled) {
- val result = TcpFastOpen.enable()?.trim()
- if (TcpFastOpen.sendEnabled) true else {
- Toast.makeText(requireContext(), if (result.isNullOrEmpty())
- getText(R.string.tcp_fastopen_failure) else result, Toast.LENGTH_SHORT).show()
- false
- }
- } else true
- }
- if (!TcpFastOpen.supported) {
- tfo.isEnabled = false
- tfo.summary = getString(R.string.tcp_fastopen_summary_unsupported, System.getProperty("os.version"))
- }
-
- hosts = findPreference(Key.hosts)!!
- hosts.summaryProvider = HostsSummaryProvider
- serviceMode = findPreference(Key.serviceMode)!!
- shareOverLan = findPreference(Key.shareOverLan)!!
- portProxy = findPreference(Key.portProxy)!!
- portProxy.setOnBindEditTextListener(EditTextPreferenceModifiers.Port)
- portLocalDns = findPreference(Key.portLocalDns)!!
- portLocalDns.setOnBindEditTextListener(EditTextPreferenceModifiers.Port)
- portTransproxy = findPreference(Key.portTransproxy)!!
- portTransproxy.setOnBindEditTextListener(EditTextPreferenceModifiers.Port)
- serviceMode.onPreferenceChangeListener = onServiceModeChange
- findPreference(Key.about)!!.summary = getString(R.string.about_title, BuildConfig.VERSION_NAME)
-
- changeState(BaseService.State.Idle) // reset everything to init state
- connection.connect(requireContext(), this)
- DataStore.publicStore.registerChangeListener(this)
- }
-
- override fun onStart() {
- super.onStart()
- connection.bandwidthTimeout = 500
- }
-
- override fun onResume() {
- super.onResume()
- fab.value = DataStore.profileId.toString()
- }
-
- private fun populateProfiles() {
- ProfileManager.ensureNotEmpty()
- val profiles = ProfileManager.getActiveProfiles()!!
- fab.value = null
- fab.entries = profiles.map { it.formattedName }.toTypedArray()
- fab.entryValues = profiles.map { it.id.toString() }.toTypedArray()
- }
-
- fun startService() {
- when {
- state != BaseService.State.Stopped -> return
- DataStore.serviceMode == Key.modeVpn -> {
- val intent = VpnService.prepare(requireContext())
- if (intent != null) startActivityForResult(intent, REQUEST_CONNECT)
- else onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null)
- }
- else -> Core.startService()
- }
- }
-
- override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) {
- when (key) {
- Key.serviceMode -> handler.post {
- connection.disconnect(requireContext())
- connection.connect(requireContext(), this)
- }
- }
- }
-
- override fun onStop() {
- connection.bandwidthTimeout = 0
- super.onStop()
- }
-
- override fun onPreferenceTreeClick(preference: Preference?) = when (preference?.key) {
- Key.id -> {
- if (state == BaseService.State.Connected) Core.stopService()
- true
- }
- Key.controlStats -> {
- tester.testConnection()
- true
- }
- Key.controlImport -> {
- startFilesForResult(Intent(Intent.ACTION_GET_CONTENT).apply {
- type = "application/*"
- putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
- putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("application/*", "text/*"))
- }, REQUEST_REPLACE_PROFILES)
- true
- }
- Key.controlExport -> {
- startFilesForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
- type = "application/json"
- putExtra(Intent.EXTRA_TITLE, "profiles.json") // optional title that can be edited
- }, REQUEST_EXPORT_PROFILES)
- true
- }
- Key.about -> {
- Toast.makeText(requireContext(), "https://shadowsocks.org/android", Toast.LENGTH_SHORT).show()
- true
- }
- Key.aboutOss -> {
- startActivity(Intent(context, OssLicensesMenuActivity::class.java))
- true
- }
- else -> super.onPreferenceTreeClick(preference)
- }
-
- override fun onDisplayPreferenceDialog(preference: Preference?) {
- if (preference != hosts || startFilesForResult(Intent(Intent.ACTION_GET_CONTENT).setType("*/*"), REQUEST_HOSTS))
- super.onDisplayPreferenceDialog(preference)
- }
-
- private fun startFilesForResult(intent: Intent, requestCode: Int): Boolean {
- try {
- startActivityForResult(intent.addCategory(Intent.CATEGORY_OPENABLE), requestCode)
- return false
- } catch (_: ActivityNotFoundException) { } catch (_: SecurityException) { }
- Toast.makeText(requireContext(), R.string.file_manager_missing, Toast.LENGTH_SHORT).show()
- return true
- }
-
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- when (requestCode) {
- REQUEST_CONNECT -> if (resultCode == Activity.RESULT_OK) Core.startService() else {
- Toast.makeText(requireContext(), R.string.vpn_permission_denied, Toast.LENGTH_SHORT).show()
- Crashlytics.log(Log.ERROR, TAG, "Failed to start VpnService from onActivityResult: $data")
- }
- REQUEST_REPLACE_PROFILES -> {
- if (resultCode != Activity.RESULT_OK) return
- val context = requireContext()
- try {
- ProfileManager.createProfilesFromJson(data!!.datas.asSequence().map {
- context.contentResolver.openInputStream(it)
- }.filterNotNull(), true)
- } catch (e: Exception) {
- printLog(e)
- Toast.makeText(context, e.readableMessage, Toast.LENGTH_SHORT).show()
- }
- populateProfiles()
- }
- REQUEST_EXPORT_PROFILES -> {
- if (resultCode != Activity.RESULT_OK) return
- val profiles = ProfileManager.serializeToJson()
- val context = requireContext()
- if (profiles != null) try {
- context.contentResolver.openOutputStream(data?.data!!)!!.bufferedWriter().use {
- it.write(profiles.toString(2))
- }
- } catch (e: Exception) {
- printLog(e)
- Toast.makeText(context, e.readableMessage, Toast.LENGTH_SHORT).show()
- }
- }
- REQUEST_HOSTS -> {
- if (resultCode != Activity.RESULT_OK) return
- val context = requireContext()
- try {
- // we read and persist all its content here to avoid content URL permission issues
- hosts.text = context.contentResolver.openInputStream(data!!.data!!)!!.bufferedReader().readText()
- } catch (e: Exception) {
- Toast.makeText(context, e.readableMessage, Toast.LENGTH_SHORT).show()
- }
- }
- else -> super.onActivityResult(requestCode, resultCode, data)
- }
- }
-
- override fun onDestroy() {
- super.onDestroy()
- DataStore.publicStore.unregisterChangeListener(this)
- val context = requireContext()
- connection.disconnect(context)
- BackupManager(context).dataChanged()
- }
-}
diff --git a/tv/src/main/java/com/github/shadowsocks/tv/ProfilesDialogFragment.kt b/tv/src/main/java/com/github/shadowsocks/tv/ProfilesDialogFragment.kt
deleted file mode 100644
index 3e7087e..0000000
--- a/tv/src/main/java/com/github/shadowsocks/tv/ProfilesDialogFragment.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-/*******************************************************************************
- * *
- * Copyright (C) 2018 by Max Lv *
- * Copyright (C) 2018 by Mygod Studio *
- * *
- * This program is free software: you can redistribute it and/or modify *
- * it under the terms of the GNU General Public License as published by *
- * the Free Software Foundation, either version 3 of the License, or *
- * (at your option) any later version. *
- * *
- * This program is distributed in the hope that it will be useful, *
- * but WITHOUT ANY WARRANTY; without even the implied warranty of *
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
- * GNU General Public License for more details. *
- * *
- * You should have received a copy of the GNU General Public License *
- * along with this program. If not, see . *
- * *
- *******************************************************************************/
-
-package com.github.shadowsocks.tv
-
-import android.os.Bundle
-import android.text.format.Formatter
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.CompoundButton
-import android.widget.TextView
-import androidx.leanback.preference.LeanbackListPreferenceDialogFragmentCompat
-import androidx.recyclerview.widget.RecyclerView
-import com.github.shadowsocks.Core
-import com.github.shadowsocks.database.ProfileManager
-import com.github.shadowsocks.plugin.PluginConfiguration
-import com.github.shadowsocks.preference.DataStore
-
-class ProfilesDialogFragment : LeanbackListPreferenceDialogFragmentCompat() {
- private inner class ProfileViewHolder(view: View) : RecyclerView.ViewHolder(view), View.OnClickListener {
- val widgetView = view.findViewById(R.id.button)
- val titleView = view.findViewById(android.R.id.title)
- init {
- view.findViewById(R.id.container).setOnClickListener(this)
- }
-
- override fun onClick(v: View) {
- val index = adapterPosition
- if (index == RecyclerView.NO_POSITION) return
- Core.switchProfile(adapter.profiles[index].id)
- (targetFragment as MainPreferenceFragment).startService()
- parentFragmentManager.popBackStack()
- adapter.notifyDataSetChanged()
- }
- }
- private inner class ProfilesAdapter : RecyclerView.Adapter() {
- val profiles = ProfileManager.getActiveProfiles()!!
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ProfileViewHolder(
- LayoutInflater.from(parent.context).inflate(R.layout.leanback_list_preference_item_single_2,
- parent, false))
-
- override fun onBindViewHolder(holder: ProfileViewHolder, position: Int) {
- val profile = profiles[position]
- holder.widgetView.isChecked = profile.id == DataStore.profileId
- holder.titleView.text = profile.formattedName
- holder.itemView.findViewById(android.R.id.summary).text = ArrayList().apply {
- if (!profile.name.isNullOrEmpty()) this += profile.formattedAddress
- val id = PluginConfiguration(profile.plugin ?: "").selected
- if (id.isNotEmpty()) this += getString(R.string.profile_plugin, id)
- if (profile.tx > 0 || profile.rx > 0) this += getString(R.string.traffic,
- Formatter.formatFileSize(activity, profile.tx), Formatter.formatFileSize(activity, profile.rx))
- }.joinToString("\n")
- }
-
- override fun getItemCount() = profiles.size
- }
-
- private val adapter = ProfilesAdapter()
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
- return super.onCreateView(inflater, container, savedInstanceState)!!.apply {
- val list = findViewById(android.R.id.list)
- list.adapter = adapter
- list.layoutManager!!.scrollToPosition(adapter.profiles.indexOfFirst { it.id == DataStore.profileId })
- }
- }
-}
diff --git a/tv/src/main/java/com/github/shadowsocks/tv/preference/LeanbackSingleListPreferenceDialogFragment.kt b/tv/src/main/java/com/github/shadowsocks/tv/preference/LeanbackSingleListPreferenceDialogFragment.kt
deleted file mode 100644
index 7600e29..0000000
--- a/tv/src/main/java/com/github/shadowsocks/tv/preference/LeanbackSingleListPreferenceDialogFragment.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*******************************************************************************
- * *
- * Copyright (C) 2018 by Max Lv *
- * Copyright (C) 2018 by Mygod Studio *
- * *
- * This program is free software: you can redistribute it and/or modify *
- * it under the terms of the GNU General Public License as published by *
- * the Free Software Foundation, either version 3 of the License, or *
- * (at your option) any later version. *
- * *
- * This program is distributed in the hope that it will be useful, *
- * but WITHOUT ANY WARRANTY; without even the implied warranty of *
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
- * GNU General Public License for more details. *
- * *
- * You should have received a copy of the GNU General Public License *
- * along with this program. If not, see . *
- * *
- *******************************************************************************/
-
-package com.github.shadowsocks.tv.preference
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.leanback.preference.LeanbackListPreferenceDialogFragmentCompat
-import androidx.recyclerview.widget.RecyclerView
-
-/**
- * Fix: scroll to selected item.
- */
-open class LeanbackSingleListPreferenceDialogFragment : LeanbackListPreferenceDialogFragmentCompat() {
- companion object {
- private val mEntryValues = LeanbackListPreferenceDialogFragmentCompat::class.java
- .getDeclaredField("mEntryValues").apply { isAccessible = true }
- private val mInitialSelection = LeanbackListPreferenceDialogFragmentCompat::class.java
- .getDeclaredField("mInitialSelection").apply { isAccessible = true }
- }
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- val selected = mInitialSelection.get(this) as? String
- val index = (mEntryValues.get(this) as? Array)?.indexOfFirst { it == selected }
- return super.onCreateView(inflater, container, savedInstanceState)!!.also {
- if (index != null) it.findViewById(android.R.id.list).layoutManager!!.scrollToPosition(index)
- }
- }
-}
diff --git a/tv/src/main/res/layout/activity_main.xml b/tv/src/main/res/layout/activity_main.xml
deleted file mode 100644
index 30e2f0b..0000000
--- a/tv/src/main/res/layout/activity_main.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
diff --git a/tv/src/main/res/layout/leanback_list_preference_item_single_2.xml b/tv/src/main/res/layout/leanback_list_preference_item_single_2.xml
deleted file mode 100644
index 65a75a9..0000000
--- a/tv/src/main/res/layout/leanback_list_preference_item_single_2.xml
+++ /dev/null
@@ -1,64 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/tv/src/main/res/values/styles.xml b/tv/src/main/res/values/styles.xml
deleted file mode 100644
index 6bad520..0000000
--- a/tv/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
diff --git a/tv/src/main/res/xml/pref_main.xml b/tv/src/main/res/xml/pref_main.xml
deleted file mode 100644
index f7c6250..0000000
--- a/tv/src/main/res/xml/pref_main.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-