mirror of https://github.com/NekoX-Dev/NekoX.git
Proxy
This commit is contained in:
parent
1cec9e7b36
commit
8727ccbd79
|
@ -0,0 +1,33 @@
|
|||
/*******************************************************************************
|
||||
* *
|
||||
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
|
||||
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
|
||||
* *
|
||||
* 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 <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
package com.github.shadowsocks.plugin
|
||||
|
||||
import android.content.pm.ResolveInfo
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.KITKAT) class NativePlugin(resolveInfo: ResolveInfo) : ResolvedPlugin(resolveInfo) {
|
||||
init {
|
||||
check(resolveInfo.providerInfo != null)
|
||||
}
|
||||
|
||||
override val componentInfo get() = resolveInfo.providerInfo!!
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
/*******************************************************************************
|
||||
* *
|
||||
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
|
||||
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
|
||||
* *
|
||||
* 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 <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
package com.github.shadowsocks.plugin
|
||||
|
||||
import android.content.ContentProvider
|
||||
import android.content.ContentValues
|
||||
import android.database.Cursor
|
||||
import android.database.MatrixCursor
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.ParcelFileDescriptor
|
||||
|
||||
/**
|
||||
* Base class for a native plugin provider. A native plugin provider offers read-only access to files that are required
|
||||
* to run a plugin, such as binary files and other configuration files. To create a native plugin provider, extend this
|
||||
* class, implement the abstract methods, and add it to your manifest like this:
|
||||
*
|
||||
* <pre class="prettyprint"><manifest>
|
||||
* ...
|
||||
* <application>
|
||||
* ...
|
||||
* <provider android:name="com.github.shadowsocks.$PLUGIN_ID.BinaryProvider"
|
||||
* android:authorities="com.github.shadowsocks.plugin.$PLUGIN_ID.BinaryProvider">
|
||||
* <intent-filter>
|
||||
* <category android:name="com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN" />
|
||||
* </intent-filter>
|
||||
* </provider>
|
||||
* ...
|
||||
* </application>
|
||||
*</manifest></pre>
|
||||
*/
|
||||
abstract class NativePluginProvider : ContentProvider() {
|
||||
override fun getType(uri: Uri): String? = "application/x-elf"
|
||||
|
||||
override fun onCreate(): Boolean = true
|
||||
|
||||
/**
|
||||
* Provide all files needed for native plugin.
|
||||
*
|
||||
* @param provider A helper object to use to add files.
|
||||
*/
|
||||
protected abstract fun populateFiles(provider: PathProvider)
|
||||
|
||||
override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?,
|
||||
sortOrder: String?): Cursor? {
|
||||
check(selection == null && selectionArgs == null && sortOrder == null)
|
||||
val result = MatrixCursor(projection)
|
||||
populateFiles(PathProvider(uri, result))
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns executable entry absolute path.
|
||||
* This is used for fast mode initialization where ss-local launches your native binary at the path given directly.
|
||||
* In order for this to work, plugin app is encouraged to have the following in its AndroidManifest.xml:
|
||||
* - android:installLocation="internalOnly" for <manifest>
|
||||
* - android:extractNativeLibs="true" for <application>
|
||||
*
|
||||
* Default behavior is throwing UnsupportedOperationException. If you don't wish to use this feature, use the
|
||||
* default behavior.
|
||||
*
|
||||
* @return Absolute path for executable entry.
|
||||
*/
|
||||
open fun getExecutable(): String = throw UnsupportedOperationException()
|
||||
|
||||
abstract fun openFile(uri: Uri): ParcelFileDescriptor
|
||||
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor {
|
||||
check(mode == "r")
|
||||
return openFile(uri)
|
||||
}
|
||||
|
||||
override fun call(method: String, arg: String?, extras: Bundle?): Bundle? = when (method) {
|
||||
PluginContract.METHOD_GET_EXECUTABLE -> Bundle().apply { putString(PluginContract.EXTRA_ENTRY, getExecutable()) }
|
||||
else -> super.call(method, arg, extras)
|
||||
}
|
||||
|
||||
// Methods that should not be used
|
||||
override fun insert(uri: Uri, values: ContentValues?): Uri? = throw UnsupportedOperationException()
|
||||
|
||||
override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int =
|
||||
throw UnsupportedOperationException()
|
||||
|
||||
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int =
|
||||
throw UnsupportedOperationException()
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.github.shadowsocks.plugin
|
||||
|
||||
import org.telegram.messenger.LocaleController
|
||||
import org.telegram.messenger.R
|
||||
|
||||
object NoPlugin : Plugin() {
|
||||
override val id: String get() = ""
|
||||
override val label: CharSequence get() = LocaleController.getString("Disable", R.string.Disable)
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*******************************************************************************
|
||||
* *
|
||||
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
|
||||
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
|
||||
* *
|
||||
* 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 <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
package com.github.shadowsocks.plugin
|
||||
|
||||
import android.database.MatrixCursor
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Helper class to provide relative paths of files to copy.
|
||||
*/
|
||||
class PathProvider internal constructor(baseUri: Uri, private val cursor: MatrixCursor) {
|
||||
private val basePath = baseUri.path?.trim('/') ?: ""
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.KITKAT) fun addPath(path: String, mode: Int = 0b110100100): PathProvider {
|
||||
val trimmed = path.trim('/')
|
||||
if (trimmed.startsWith(basePath)) cursor.newRow()
|
||||
.add(PluginContract.COLUMN_PATH, trimmed)
|
||||
.add(PluginContract.COLUMN_MODE, mode)
|
||||
return this
|
||||
}
|
||||
@RequiresApi(Build.VERSION_CODES.KITKAT) fun addTo(file: File, to: String = "", mode: Int = 0b110100100): PathProvider {
|
||||
var sub = to + file.name
|
||||
if (basePath.startsWith(sub)) if (file.isDirectory) {
|
||||
sub += '/'
|
||||
file.listFiles()!!.forEach { addTo(it, sub, mode) }
|
||||
} else addPath(sub, mode)
|
||||
return this
|
||||
}
|
||||
fun addAt(file: File, at: String = "", mode: Int = 0b110100100): PathProvider {
|
||||
if (basePath.startsWith(at)) {
|
||||
if (file.isDirectory) file.listFiles()!!.forEach { addTo(it, at, mode) } else addPath(at, mode)
|
||||
}
|
||||
return this
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*******************************************************************************
|
||||
* *
|
||||
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
|
||||
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
|
||||
* *
|
||||
* 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 <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
package com.github.shadowsocks.plugin
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
abstract class Plugin {
|
||||
abstract val id: String
|
||||
open val idAliases get() = emptyArray<String>()
|
||||
abstract val label: CharSequence
|
||||
open val icon: Drawable? get() = null
|
||||
open val defaultConfig: String? get() = null
|
||||
open val packageName: String get() = ""
|
||||
open val trusted: Boolean get() = true
|
||||
open val directBootAware: Boolean get() = true
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
return id == (other as Plugin).id
|
||||
}
|
||||
override fun hashCode() = id.hashCode()
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*******************************************************************************
|
||||
* *
|
||||
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
|
||||
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
|
||||
* *
|
||||
* 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 <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
package com.github.shadowsocks.plugin
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.github.shadowsocks.utils.Commandline
|
||||
import org.telegram.messenger.FileLog
|
||||
import org.telegram.messenger.LocaleController
|
||||
import org.telegram.messenger.R
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
class PluginConfiguration(val pluginsOptions: MutableMap<String, PluginOptions>, var selected: String) {
|
||||
private constructor(plugins: List<PluginOptions>) : this(
|
||||
HashMap(plugins.filter { it.id.isNotEmpty() }.associateBy { it.id }),
|
||||
if (plugins.isEmpty()) "" else plugins[0].id)
|
||||
|
||||
constructor(plugin: String) : this(plugin.split('\n').map { line ->
|
||||
if (line.startsWith("kcptun ")) {
|
||||
val opt = PluginOptions()
|
||||
opt.id = "kcptun"
|
||||
try {
|
||||
val iterator = Commandline.translateCommandline(line).drop(1).iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val option = iterator.next()
|
||||
when {
|
||||
option == "--nocomp" -> opt["nocomp"] = null
|
||||
option.startsWith("--") -> opt[option.substring(2)] = iterator.next()
|
||||
else -> throw IllegalArgumentException("Unknown kcptun parameter: $option")
|
||||
}
|
||||
}
|
||||
} catch (exc: Exception) {
|
||||
FileLog.e(exc)
|
||||
}
|
||||
opt
|
||||
} else PluginOptions(line)
|
||||
})
|
||||
|
||||
val selectedName get() = selected.takeIf { it.isNotBlank() } ?: LocaleController.getString("Disable", R.string.Disable)
|
||||
|
||||
@JvmOverloads
|
||||
fun getOptions(
|
||||
id: String = selected,
|
||||
defaultConfig: () -> String? = { PluginManager.fetchPlugins().lookup[id]?.defaultConfig }
|
||||
) = if (id.isEmpty()) PluginOptions() else pluginsOptions[id] ?: PluginOptions(id, defaultConfig())
|
||||
|
||||
override fun toString(): String {
|
||||
val result = LinkedList<PluginOptions>()
|
||||
for ((id, opt) in pluginsOptions) if (id == this.selected) result.addFirst(opt) else result.addLast(opt)
|
||||
if (!pluginsOptions.contains(selected)) result.addFirst(getOptions())
|
||||
return result.joinToString("\n") { it.toString(false) }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
/*******************************************************************************
|
||||
* *
|
||||
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
|
||||
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
|
||||
* *
|
||||
* 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 <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
package com.github.shadowsocks.plugin
|
||||
|
||||
/**
|
||||
* The contract between the plugin provider and host. Contains definitions for the supported actions, extras, etc.
|
||||
*
|
||||
* This class is written in Java to keep Java interoperability.
|
||||
*/
|
||||
object PluginContract {
|
||||
/**
|
||||
* ContentProvider Action: Used for NativePluginProvider.
|
||||
*
|
||||
* Constant Value: "com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN"
|
||||
*/
|
||||
const val ACTION_NATIVE_PLUGIN = "com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN"
|
||||
|
||||
/**
|
||||
* Activity Action: Used for ConfigurationActivity.
|
||||
*
|
||||
* Constant Value: "com.github.shadowsocks.plugin.ACTION_CONFIGURE"
|
||||
*/
|
||||
const val ACTION_CONFIGURE = "com.github.shadowsocks.plugin.ACTION_CONFIGURE"
|
||||
/**
|
||||
* Activity Action: Used for HelpActivity or HelpCallback.
|
||||
*
|
||||
* Constant Value: "com.github.shadowsocks.plugin.ACTION_HELP"
|
||||
*/
|
||||
const val ACTION_HELP = "com.github.shadowsocks.plugin.ACTION_HELP"
|
||||
|
||||
/**
|
||||
* The lookup key for a string that provides the plugin entry binary.
|
||||
*
|
||||
* Example: "/data/data/com.github.shadowsocks.plugin.obfs_local/lib/libobfs-local.so"
|
||||
*
|
||||
* Constant Value: "com.github.shadowsocks.plugin.EXTRA_ENTRY"
|
||||
*/
|
||||
const val EXTRA_ENTRY = "com.github.shadowsocks.plugin.EXTRA_ENTRY"
|
||||
/**
|
||||
* The lookup key for a string that provides the options as a string.
|
||||
*
|
||||
* Example: "obfs=http;obfs-host=www.baidu.com"
|
||||
*
|
||||
* Constant Value: "com.github.shadowsocks.plugin.EXTRA_OPTIONS"
|
||||
*/
|
||||
const val EXTRA_OPTIONS = "com.github.shadowsocks.plugin.EXTRA_OPTIONS"
|
||||
/**
|
||||
* The lookup key for a CharSequence that provides user relevant help message.
|
||||
*
|
||||
* Example: "obfs=<http></http>|tls> Enable obfuscating: HTTP or TLS (Experimental).
|
||||
* obfs-host=<host_name> Hostname for obfuscating (Experimental)."
|
||||
*
|
||||
* Constant Value: "com.github.shadowsocks.plugin.EXTRA_HELP_MESSAGE"
|
||||
</host_name> */
|
||||
const val EXTRA_HELP_MESSAGE = "com.github.shadowsocks.plugin.EXTRA_HELP_MESSAGE"
|
||||
|
||||
/**
|
||||
* The metadata key to retrieve plugin id. Required for plugins.
|
||||
*
|
||||
* Constant Value: "com.github.shadowsocks.plugin.id"
|
||||
*/
|
||||
const val METADATA_KEY_ID = "com.github.shadowsocks.plugin.id"
|
||||
/**
|
||||
* The metadata key to retrieve plugin id aliases.
|
||||
* Can be a string (representing one alias) or a resource to a string or string array.
|
||||
*
|
||||
* Constant Value: "com.github.shadowsocks.plugin.id.aliases"
|
||||
*/
|
||||
const val METADATA_KEY_ID_ALIASES = "com.github.shadowsocks.plugin.id.aliases"
|
||||
/**
|
||||
* The metadata key to retrieve default configuration. Default value is empty.
|
||||
*
|
||||
* Constant Value: "com.github.shadowsocks.plugin.default_config"
|
||||
*/
|
||||
const val METADATA_KEY_DEFAULT_CONFIG = "com.github.shadowsocks.plugin.default_config"
|
||||
/**
|
||||
* The metadata key to retrieve executable path to your native binary.
|
||||
* This path should be relative to your application's nativeLibraryDir.
|
||||
*
|
||||
* If this is set, the host app will prefer this value and (probably) not launch your app at all (aka faster mode).
|
||||
* In order for this to work, plugin app is encouraged to have the following in its AndroidManifest.xml:
|
||||
* - android:installLocation="internalOnly" for <manifest>
|
||||
* - android:extractNativeLibs="true" for <application>
|
||||
*
|
||||
* Do not use this if you plan to do some setup work before giving away your binary path,
|
||||
* or your native binary is not at a fixed location relative to your application's nativeLibraryDir.
|
||||
*
|
||||
* Since plugin lib: 1.3.0
|
||||
*
|
||||
* Constant Value: "com.github.shadowsocks.plugin.executable_path"
|
||||
*/
|
||||
const val METADATA_KEY_EXECUTABLE_PATH = "com.github.shadowsocks.plugin.executable_path"
|
||||
|
||||
const val METHOD_GET_EXECUTABLE = "shadowsocks:getExecutable"
|
||||
|
||||
/** ConfigurationActivity result: fallback to manual edit mode. */
|
||||
const val RESULT_FALLBACK = 1
|
||||
|
||||
/**
|
||||
* Relative to the file to be copied. This column is required.
|
||||
*
|
||||
* Example: "kcptun", "doc/help.txt"
|
||||
*
|
||||
* Type: String
|
||||
*/
|
||||
const val COLUMN_PATH = "path"
|
||||
/**
|
||||
* File mode bits. Default value is 644 in octal.
|
||||
*
|
||||
* Example: 0b110100100 (for 755 in octal)
|
||||
*
|
||||
* Type: Int or String (deprecated)
|
||||
*/
|
||||
const val COLUMN_MODE = "mode"
|
||||
|
||||
/**
|
||||
* The scheme for general plugin actions.
|
||||
*/
|
||||
const val SCHEME = "plugin"
|
||||
/**
|
||||
* The authority for general plugin actions.
|
||||
*/
|
||||
const val AUTHORITY = "com.github.shadowsocks"
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*******************************************************************************
|
||||
* *
|
||||
* Copyright (C) 2020 by Max Lv <max.c.lv@gmail.com> *
|
||||
* Copyright (C) 2020 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
|
||||
* *
|
||||
* 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 <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
package com.github.shadowsocks.plugin
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import org.telegram.messenger.ApplicationLoader
|
||||
import org.telegram.messenger.LocaleController
|
||||
import org.telegram.messenger.R
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.KITKAT) class PluginList : ArrayList<Plugin>() {
|
||||
init {
|
||||
add(NoPlugin)
|
||||
addAll(ApplicationLoader.applicationContext.packageManager.queryIntentContentProviders(
|
||||
Intent(PluginContract.ACTION_NATIVE_PLUGIN), PackageManager.GET_META_DATA).map { NativePlugin(it) })
|
||||
}
|
||||
|
||||
val lookup = mutableMapOf<String, Plugin>().apply {
|
||||
for (plugin in this@PluginList) {
|
||||
fun check(old: Plugin?) = check(old == null || old === plugin) { LocaleController.formatString("SSPluginConflictingName",R.string.SSPluginConflictingName,plugin.id) }
|
||||
check(put(plugin.id, plugin))
|
||||
for (alias in plugin.idAliases) check(put(alias, plugin))
|
||||
}
|
||||
}
|
||||
val lookupNames get() = lookup.values.map {
|
||||
if (it.label.isNotBlank()) {
|
||||
"${it.label} (${it.id})"
|
||||
} else {
|
||||
it.id
|
||||
}
|
||||
}.map { it.takeIf { it.isNotBlank() } ?: LocaleController.getString("Disable", R.string.Disable) }.toTypedArray()
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
/*******************************************************************************
|
||||
* *
|
||||
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
|
||||
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
|
||||
* *
|
||||
* 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 <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
package com.github.shadowsocks.plugin
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.ContentResolver
|
||||
import android.content.Intent
|
||||
import android.content.pm.ComponentInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.ProviderInfo
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.system.Os
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.github.shadowsocks.utils.listenForPackageChanges
|
||||
import org.telegram.messenger.ApplicationLoader
|
||||
import org.telegram.messenger.FileLog
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
object PluginManager {
|
||||
|
||||
val app get() = ApplicationLoader.applicationContext
|
||||
|
||||
fun bundleOf(pair: Pair<String, String>) = Bundle().apply { putString(pair.first, pair.second) }
|
||||
|
||||
class PluginNotFoundException(plugin: String) : FileNotFoundException(plugin) {
|
||||
}
|
||||
|
||||
private var receiver: BroadcastReceiver? = null
|
||||
private var cachedPlugins: PluginList? = null
|
||||
|
||||
@JvmStatic
|
||||
fun fetchPlugins() = synchronized(this) {
|
||||
if (receiver == null) receiver = ApplicationLoader.applicationContext.listenForPackageChanges {
|
||||
synchronized(this) {
|
||||
receiver = null
|
||||
cachedPlugins = null
|
||||
}
|
||||
}
|
||||
if (cachedPlugins == null) cachedPlugins = PluginList()
|
||||
cachedPlugins!!
|
||||
}
|
||||
|
||||
private fun buildUri(id: String) = Uri.Builder()
|
||||
.scheme(PluginContract.SCHEME)
|
||||
.authority(PluginContract.AUTHORITY)
|
||||
.path("/$id")
|
||||
.build()
|
||||
|
||||
@JvmStatic
|
||||
fun buildIntent(id: String, action: String): Intent = Intent(action, buildUri(id))
|
||||
|
||||
// the following parts are meant to be used by :bg
|
||||
@Throws(Throwable::class)
|
||||
fun init(configuration: PluginConfiguration): Pair<String, PluginOptions>? {
|
||||
if (configuration.selected.isEmpty()) return null
|
||||
var throwable: Throwable? = null
|
||||
|
||||
try {
|
||||
val result = initNative(configuration)
|
||||
if (result != null) return result
|
||||
} catch (t: Throwable) {
|
||||
if (throwable == null) throwable = t else FileLog.e(t)
|
||||
}
|
||||
|
||||
// add other plugin types here
|
||||
|
||||
throw throwable ?: PluginNotFoundException(configuration.selected)
|
||||
}
|
||||
|
||||
private fun initNative(configuration: PluginConfiguration): Pair<String, PluginOptions>? {
|
||||
var flags = PackageManager.GET_META_DATA
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
flags = flags or PackageManager.MATCH_DIRECT_BOOT_UNAWARE or PackageManager.MATCH_DIRECT_BOOT_AWARE
|
||||
}
|
||||
val providers = app.packageManager.queryIntentContentProviders(
|
||||
Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(configuration.selected)), flags)
|
||||
if (providers.isEmpty()) return null
|
||||
val provider = providers.single().providerInfo
|
||||
val options = configuration.getOptions { provider.loadString(PluginContract.METADATA_KEY_DEFAULT_CONFIG) }
|
||||
var failure: Throwable? = null
|
||||
try {
|
||||
initNativeFaster(provider)?.also { return it to options }
|
||||
} catch (t: Throwable) {
|
||||
FileLog.w("Initializing native plugin faster mode failed")
|
||||
failure = t
|
||||
}
|
||||
|
||||
val uri = Uri.Builder().apply {
|
||||
scheme(ContentResolver.SCHEME_CONTENT)
|
||||
authority(provider.authority)
|
||||
}.build()
|
||||
try {
|
||||
return initNativeFast(app.contentResolver, options, uri)?.let { it to options }
|
||||
} catch (t: Throwable) {
|
||||
FileLog.w("Initializing native plugin fast mode failed")
|
||||
failure?.also { t.addSuppressed(it) }
|
||||
failure = t
|
||||
}
|
||||
|
||||
try {
|
||||
return initNativeSlow(app.contentResolver, options, uri)?.let { it to options }
|
||||
} catch (t: Throwable) {
|
||||
failure?.also { t.addSuppressed(it) }
|
||||
throw t
|
||||
}
|
||||
}
|
||||
|
||||
private fun initNativeFaster(provider: ProviderInfo): String? {
|
||||
return provider.loadString(PluginContract.METADATA_KEY_EXECUTABLE_PATH)?.let { relativePath ->
|
||||
File(provider.applicationInfo.nativeLibraryDir).resolve(relativePath).apply {
|
||||
check(canExecute())
|
||||
}.absolutePath
|
||||
}
|
||||
}
|
||||
|
||||
private fun initNativeFast(cr: ContentResolver, options: PluginOptions, uri: Uri): String? {
|
||||
return cr.call(uri, PluginContract.METHOD_GET_EXECUTABLE, null,
|
||||
bundleOf(PluginContract.EXTRA_OPTIONS to options.id))?.getString(PluginContract.EXTRA_ENTRY)?.also {
|
||||
check(File(it).canExecute())
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("Recycle")
|
||||
private fun initNativeSlow(cr: ContentResolver, options: PluginOptions, uri: Uri): String? {
|
||||
var initialized = false
|
||||
fun entryNotFound(): Nothing = throw IndexOutOfBoundsException("Plugin entry binary not found")
|
||||
val pluginDir = File(app.noBackupFilesDir, "plugin")
|
||||
(cr.query(uri, arrayOf(PluginContract.COLUMN_PATH, PluginContract.COLUMN_MODE), null, null, null)
|
||||
?: return null).use { cursor ->
|
||||
if (!cursor.moveToFirst()) entryNotFound()
|
||||
pluginDir.deleteRecursively()
|
||||
if (!pluginDir.mkdirs()) throw FileNotFoundException("Unable to create plugin directory")
|
||||
val pluginDirPath = pluginDir.absolutePath + '/'
|
||||
do {
|
||||
val path = cursor.getString(0)
|
||||
val file = File(pluginDir, path)
|
||||
check(file.absolutePath.startsWith(pluginDirPath))
|
||||
cr.openInputStream(uri.buildUpon().path(path).build())!!.use { inStream ->
|
||||
file.outputStream().use { outStream -> inStream.copyTo(outStream) }
|
||||
}
|
||||
Os.chmod(file.absolutePath, when (cursor.getType(1)) {
|
||||
Cursor.FIELD_TYPE_INTEGER -> cursor.getInt(1)
|
||||
Cursor.FIELD_TYPE_STRING -> cursor.getString(1).toInt(8)
|
||||
else -> throw IllegalArgumentException("File mode should be of type int")
|
||||
})
|
||||
if (path == options.id) initialized = true
|
||||
} while (cursor.moveToNext())
|
||||
}
|
||||
if (!initialized) entryNotFound()
|
||||
return File(pluginDir, options.id).absolutePath
|
||||
}
|
||||
|
||||
fun ComponentInfo.loadString(key: String) = when (val value = metaData.get(key)) {
|
||||
is String -> value
|
||||
is Int -> app.packageManager.getResourcesForApplication(applicationInfo).getString(value)
|
||||
null -> null
|
||||
else -> error("meta-data $key has invalid type ${value.javaClass}")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
/*******************************************************************************
|
||||
* *
|
||||
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
|
||||
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
|
||||
* *
|
||||
* 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 <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
package com.github.shadowsocks.plugin
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Helper class for processing plugin options.
|
||||
*
|
||||
* Based on: https://github.com/apache/ant/blob/588ce1f/src/main/org/apache/tools/ant/types/Commandline.java
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
class PluginOptions : HashMap<String, String?> {
|
||||
var id = ""
|
||||
|
||||
constructor() : super()
|
||||
constructor(initialCapacity: Int) : super(initialCapacity)
|
||||
constructor(initialCapacity: Int, loadFactor: Float) : super(initialCapacity, loadFactor)
|
||||
|
||||
private constructor(options: String?, parseId: Boolean) : this() {
|
||||
@Suppress("NAME_SHADOWING")
|
||||
var parseId = parseId
|
||||
if (options.isNullOrEmpty()) return
|
||||
check(options.all { !it.isISOControl() }) { "No control characters allowed." }
|
||||
val tokenizer = StringTokenizer("$options;", "\\=;", true)
|
||||
val current = StringBuilder()
|
||||
var key: String? = null
|
||||
while (tokenizer.hasMoreTokens()) when (val nextToken = tokenizer.nextToken()) {
|
||||
"\\" -> current.append(tokenizer.nextToken())
|
||||
"=" -> if (key == null) {
|
||||
key = current.toString()
|
||||
current.setLength(0)
|
||||
} else current.append(nextToken)
|
||||
";" -> {
|
||||
if (key != null) {
|
||||
put(key, current.toString())
|
||||
key = null
|
||||
} else if (current.isNotEmpty())
|
||||
if (parseId) id = current.toString() else put(current.toString(), null)
|
||||
current.setLength(0)
|
||||
parseId = false
|
||||
}
|
||||
else -> current.append(nextToken)
|
||||
}
|
||||
}
|
||||
|
||||
constructor(options: String?) : this(options, true)
|
||||
constructor(id: String, options: String?) : this(options, false) {
|
||||
this.id = id
|
||||
}
|
||||
|
||||
/**
|
||||
* Put but if value is null or default, the entry is deleted.
|
||||
*
|
||||
* @return Old value before put.
|
||||
*/
|
||||
fun putWithDefault(key: String, value: String?, default: String? = null) =
|
||||
if (value == null || value == default) remove(key) else put(key, value)
|
||||
|
||||
private fun append(result: StringBuilder, str: String) = str.indices.map { str[it] }.forEach {
|
||||
when (it) {
|
||||
'\\', '=', ';' -> {
|
||||
result.append('\\') // intentionally no break
|
||||
result.append(it)
|
||||
}
|
||||
else -> result.append(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun toString(trimId: Boolean): String {
|
||||
val result = StringBuilder()
|
||||
if (!trimId) if (id.isEmpty()) return "" else append(result, id)
|
||||
for ((key, value) in entries) {
|
||||
if (result.isNotEmpty()) result.append(';')
|
||||
append(result, key)
|
||||
if (value != null) {
|
||||
result.append('=')
|
||||
append(result, value)
|
||||
}
|
||||
}
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
override fun toString(): String = toString(true)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
return javaClass == other?.javaClass && super.equals(other) && id == (other as PluginOptions).id
|
||||
}
|
||||
override fun hashCode(): Int = Objects.hash(super.hashCode(), id)
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*******************************************************************************
|
||||
* *
|
||||
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
|
||||
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
|
||||
* *
|
||||
* 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 <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
package com.github.shadowsocks.plugin
|
||||
|
||||
import android.content.pm.ComponentInfo
|
||||
import android.content.pm.ResolveInfo
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.github.shadowsocks.plugin.PluginManager.app
|
||||
import com.github.shadowsocks.plugin.PluginManager.loadString
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
abstract class ResolvedPlugin(protected val resolveInfo: ResolveInfo) : Plugin() {
|
||||
protected abstract val componentInfo: ComponentInfo
|
||||
|
||||
override val id by lazy { componentInfo.loadString(PluginContract.METADATA_KEY_ID)!! }
|
||||
override val idAliases: Array<String> by lazy {
|
||||
when (val value = componentInfo.metaData.get(PluginContract.METADATA_KEY_ID_ALIASES)) {
|
||||
is String -> arrayOf(value)
|
||||
is Int -> app.packageManager.getResourcesForApplication(componentInfo.applicationInfo).run {
|
||||
when (getResourceTypeName(value)) {
|
||||
"string" -> arrayOf(getString(value))
|
||||
else -> getStringArray(value)
|
||||
}
|
||||
}
|
||||
null -> emptyArray()
|
||||
else -> error("unknown type for plugin meta-data idAliases")
|
||||
}
|
||||
}
|
||||
override val label: CharSequence get() = resolveInfo.loadLabel(app.packageManager)
|
||||
override val icon: Drawable get() = resolveInfo.loadIcon(app.packageManager)
|
||||
override val defaultConfig by lazy { componentInfo.loadString(PluginContract.METADATA_KEY_DEFAULT_CONFIG) }
|
||||
override val packageName: String get() = componentInfo.packageName
|
||||
override val trusted = true
|
||||
override val directBootAware get() = Build.VERSION.SDK_INT < 24 || componentInfo.directBootAware
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
/*******************************************************************************
|
||||
* *
|
||||
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
|
||||
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
|
||||
* *
|
||||
* 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 <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
package com.github.shadowsocks.utils
|
||||
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Commandline objects help handling command lines specifying processes to
|
||||
* execute.
|
||||
*
|
||||
* The class can be used to define a command line as nested elements or as a
|
||||
* helper to define a command line by an application.
|
||||
*
|
||||
*
|
||||
* `
|
||||
* <someelement><br></br>
|
||||
* <acommandline executable="/executable/to/run"><br></br>
|
||||
* <argument value="argument 1" /><br></br>
|
||||
* <argument line="argument_1 argument_2 argument_3" /><br></br>
|
||||
* <argument value="argument 4" /><br></br>
|
||||
* </acommandline><br></br>
|
||||
* </someelement><br></br>
|
||||
` *
|
||||
*
|
||||
* Based on: https://github.com/apache/ant/blob/588ce1f/src/main/org/apache/tools/ant/types/Commandline.java
|
||||
*
|
||||
* Adds support for escape character '\'.
|
||||
*/
|
||||
object Commandline {
|
||||
|
||||
/**
|
||||
* Quote the parts of the given array in way that makes them
|
||||
* usable as command line arguments.
|
||||
* @param args the list of arguments to quote.
|
||||
* @return empty string for null or no command, else every argument split
|
||||
* by spaces and quoted by quoting rules.
|
||||
*/
|
||||
fun toString(args: Iterable<String>?): String {
|
||||
// empty path return empty string
|
||||
args ?: return ""
|
||||
// path containing one or more elements
|
||||
val result = StringBuilder()
|
||||
for (arg in args) {
|
||||
if (result.isNotEmpty()) result.append(' ')
|
||||
arg.indices.map { arg[it] }.forEach {
|
||||
when (it) {
|
||||
' ', '\\', '"', '\'' -> {
|
||||
result.append('\\') // intentionally no break
|
||||
result.append(it)
|
||||
}
|
||||
else -> result.append(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Quote the parts of the given array in way that makes them
|
||||
* usable as command line arguments.
|
||||
* @param args the list of arguments to quote.
|
||||
* @return empty string for null or no command, else every argument split
|
||||
* by spaces and quoted by quoting rules.
|
||||
*/
|
||||
fun toString(args: Array<String>) = toString(args.asIterable()) // thanks to Java, arrays aren't iterable
|
||||
|
||||
/**
|
||||
* Crack a command line.
|
||||
* @param toProcess the command line to process.
|
||||
* @return the command line broken into strings.
|
||||
* An empty or null toProcess parameter results in a zero sized array.
|
||||
*/
|
||||
fun translateCommandline(toProcess: String?): Array<String> {
|
||||
if (toProcess == null || toProcess.isEmpty()) {
|
||||
//no command? no string
|
||||
return arrayOf()
|
||||
}
|
||||
// parse with a simple finite state machine
|
||||
|
||||
val normal = 0
|
||||
val inQuote = 1
|
||||
val inDoubleQuote = 2
|
||||
var state = normal
|
||||
val tok = StringTokenizer(toProcess, "\\\"\' ", true)
|
||||
val result = ArrayList<String>()
|
||||
val current = StringBuilder()
|
||||
var lastTokenHasBeenQuoted = false
|
||||
var lastTokenIsSlash = false
|
||||
|
||||
while (tok.hasMoreTokens()) {
|
||||
val nextTok = tok.nextToken()
|
||||
when (state) {
|
||||
inQuote -> if ("\'" == nextTok) {
|
||||
lastTokenHasBeenQuoted = true
|
||||
state = normal
|
||||
} else current.append(nextTok)
|
||||
inDoubleQuote -> when (nextTok) {
|
||||
"\"" -> if (lastTokenIsSlash) {
|
||||
current.append(nextTok)
|
||||
lastTokenIsSlash = false
|
||||
} else {
|
||||
lastTokenHasBeenQuoted = true
|
||||
state = normal
|
||||
}
|
||||
"\\" -> lastTokenIsSlash = if (lastTokenIsSlash) {
|
||||
current.append(nextTok)
|
||||
false
|
||||
} else true
|
||||
else -> {
|
||||
if (lastTokenIsSlash) {
|
||||
current.append("\\") // unescaped
|
||||
lastTokenIsSlash = false
|
||||
}
|
||||
current.append(nextTok)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
when {
|
||||
lastTokenIsSlash -> {
|
||||
current.append(nextTok)
|
||||
lastTokenIsSlash = false
|
||||
}
|
||||
"\\" == nextTok -> lastTokenIsSlash = true
|
||||
"\'" == nextTok -> state = inQuote
|
||||
"\"" == nextTok -> state = inDoubleQuote
|
||||
" " == nextTok -> if (lastTokenHasBeenQuoted || current.isNotEmpty()) {
|
||||
result.add(current.toString())
|
||||
current.setLength(0)
|
||||
}
|
||||
else -> current.append(nextTok)
|
||||
}
|
||||
lastTokenHasBeenQuoted = false
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lastTokenHasBeenQuoted || current.isNotEmpty()) result.add(current.toString())
|
||||
require(state != inQuote && state != inDoubleQuote) { "unbalanced quotes in $toProcess" }
|
||||
require(!lastTokenIsSlash) { "escape character following nothing in $toProcess" }
|
||||
return result.toTypedArray()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
/*******************************************************************************
|
||||
* *
|
||||
* Copyright (C) 2018 by Max Lv <max.c.lv@gmail.com> *
|
||||
* Copyright (C) 2018 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
|
||||
* *
|
||||
* 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 <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
package com.github.shadowsocks.utils
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.*
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.res.Resources
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.ImageDecoder
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.system.Os
|
||||
import android.system.OsConstants
|
||||
import android.util.TypedValue
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.RequiresApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import org.telegram.messenger.FileLog
|
||||
import java.io.FileDescriptor
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.InetAddress
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
fun <T> Iterable<T>.forEachTry(action: (T) -> Unit) {
|
||||
var result: Exception? = null
|
||||
for (element in this) try {
|
||||
action(element)
|
||||
} catch (e: Exception) {
|
||||
if (result == null) result = e else result.addSuppressed(e)
|
||||
}
|
||||
if (result != null) {
|
||||
FileLog.e(result)
|
||||
throw result
|
||||
}
|
||||
}
|
||||
|
||||
val Throwable.readableMessage get() = localizedMessage ?: javaClass.name
|
||||
|
||||
/**
|
||||
* https://android.googlesource.com/platform/prebuilts/runtime/+/94fec32/appcompat/hiddenapi-light-greylist.txt#9466
|
||||
*/
|
||||
private val getInt = FileDescriptor::class.java.getDeclaredMethod("getInt$")
|
||||
val FileDescriptor.int get() = getInt.invoke(this) as Int
|
||||
|
||||
private val parseNumericAddress by lazy @SuppressLint("SoonBlockedPrivateApi") {
|
||||
InetAddress::class.java.getDeclaredMethod("parseNumericAddress", String::class.java).apply {
|
||||
isAccessible = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A slightly more performant variant of parseNumericAddress.
|
||||
*
|
||||
* Bug in Android 9.0 and lower: https://issuetracker.google.com/issues/123456213
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) fun String?.parseNumericAddress(): InetAddress? = Os.inet_pton(OsConstants.AF_INET, this)
|
||||
?: Os.inet_pton(OsConstants.AF_INET6, this)?.let {
|
||||
if (Build.VERSION.SDK_INT >= 29) it else parseNumericAddress.invoke(null, this) as InetAddress
|
||||
}
|
||||
|
||||
suspend fun <T> HttpURLConnection.useCancellable(block: suspend HttpURLConnection.() -> T): T {
|
||||
return suspendCancellableCoroutine { cont ->
|
||||
cont.invokeOnCancellation {
|
||||
if (Build.VERSION.SDK_INT >= 26) disconnect() else GlobalScope.launch(Dispatchers.IO) { disconnect() }
|
||||
}
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
cont.resume(block())
|
||||
} catch (e: Throwable) {
|
||||
cont.resumeWithException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun parsePort(str: String?, default: Int, min: Int = 1025): Int {
|
||||
val value = str?.toIntOrNull() ?: default
|
||||
return if (value < min || value > 65535) default else value
|
||||
}
|
||||
|
||||
fun broadcastReceiver(callback: (Context, Intent) -> Unit): BroadcastReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) = callback(context, intent)
|
||||
}
|
||||
|
||||
fun Context.listenForPackageChanges(onetime: Boolean = true, callback: () -> Unit) = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
callback()
|
||||
if (onetime) context.unregisterReceiver(this)
|
||||
}
|
||||
}.apply {
|
||||
registerReceiver(this, IntentFilter().apply {
|
||||
addAction(Intent.ACTION_PACKAGE_ADDED)
|
||||
addAction(Intent.ACTION_PACKAGE_REMOVED)
|
||||
addDataScheme("package")
|
||||
})
|
||||
}
|
||||
|
||||
fun ContentResolver.openBitmap(uri: Uri) =
|
||||
if (Build.VERSION.SDK_INT >= 28) ImageDecoder.decodeBitmap(ImageDecoder.createSource(this, uri))
|
||||
else BitmapFactory.decodeStream(openInputStream(uri))
|
||||
|
||||
val PackageInfo.signaturesCompat
|
||||
get() =
|
||||
if (Build.VERSION.SDK_INT >= 28) signingInfo.apkContentsSigners else @Suppress("DEPRECATION") signatures
|
||||
|
||||
/**
|
||||
* Based on: https://stackoverflow.com/a/26348729/2245107
|
||||
*/
|
||||
fun Resources.Theme.resolveResourceId(@AttrRes resId: Int): Int {
|
||||
val typedValue = TypedValue()
|
||||
if (!resolveAttribute(resId, typedValue, true)) throw Resources.NotFoundException()
|
||||
return typedValue.resourceId
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package com.v2ray.ang
|
||||
|
||||
/**
|
||||
*
|
||||
* App Config Const
|
||||
*/
|
||||
object V2RayConfig {
|
||||
const val ANG_PACKAGE = "com.v2ray.ang"
|
||||
const val ANG_CONFIG = "ang_config"
|
||||
const val PREF_CURR_CONFIG = "pref_v2ray_config"
|
||||
const val PREF_CURR_CONFIG_GUID = "pref_v2ray_config_guid"
|
||||
const val PREF_CURR_CONFIG_NAME = "pref_v2ray_config_name"
|
||||
const val PREF_CURR_CONFIG_DOMAIN = "pref_v2ray_config_domain"
|
||||
const val PREF_INAPP_BUY_IS_PREMIUM = "pref_inapp_buy_is_premium"
|
||||
const val VMESS_PROTOCOL: String = "vmess://"
|
||||
const val VMESS1_PROTOCOL = "vmess1://"
|
||||
const val SS_PROTOCOL: String = "ss://"
|
||||
const val SSR_PROTOCOL: String = "ssr://"
|
||||
const val RB_PROTOCOL: String = "rb://"
|
||||
|
||||
const val SOCKS_PROTOCOL: String = "socks://"
|
||||
const val BROADCAST_ACTION_SERVICE = "com.v2ray.ang.action.service"
|
||||
const val BROADCAST_ACTION_ACTIVITY = "com.v2ray.ang.action.activity"
|
||||
const val BROADCAST_ACTION_WIDGET_CLICK = "com.v2ray.ang.action.widget.click"
|
||||
|
||||
const val TASKER_EXTRA_BUNDLE = "com.twofortyfouram.locale.intent.extra.BUNDLE"
|
||||
const val TASKER_EXTRA_STRING_BLURB = "com.twofortyfouram.locale.intent.extra.BLURB"
|
||||
const val TASKER_EXTRA_BUNDLE_SWITCH = "tasker_extra_bundle_switch"
|
||||
const val TASKER_EXTRA_BUNDLE_GUID = "tasker_extra_bundle_guid"
|
||||
const val TASKER_DEFAULT_GUID = "Default"
|
||||
|
||||
const val PREF_V2RAY_ROUTING_AGENT = "pref_v2ray_routing_agent"
|
||||
const val PREF_V2RAY_ROUTING_DIRECT = "pref_v2ray_routing_direct"
|
||||
const val PREF_V2RAY_ROUTING_BLOCKED = "pref_v2ray_routing_blocked"
|
||||
const val TAG_AGENT = "proxy"
|
||||
const val TAG_DIRECT = "direct"
|
||||
const val TAG_BLOCKED = "block"
|
||||
|
||||
const val androidpackagenamelistUrl = "https://raw.githubusercontent.com/2dust/androidpackagenamelist/master/proxy.txt"
|
||||
const val v2rayCustomRoutingListUrl = "https://raw.githubusercontent.com/2dust/v2rayCustomRoutingList/master/"
|
||||
const val v2rayNGIssues = "https://github.com/2dust/v2rayNG/issues"
|
||||
const val promotionUrl = "https://1.2345345.xyz/ads.html"
|
||||
|
||||
const val DNS_AGENT = "1.1.1.1"
|
||||
const val DNS_DIRECT = "223.5.5.5"
|
||||
|
||||
const val MSG_REGISTER_CLIENT = 1
|
||||
const val MSG_STATE_RUNNING = 11
|
||||
const val MSG_STATE_NOT_RUNNING = 12
|
||||
const val MSG_UNREGISTER_CLIENT = 2
|
||||
const val MSG_STATE_START = 3
|
||||
const val MSG_STATE_START_SUCCESS = 31
|
||||
const val MSG_STATE_START_FAILURE = 32
|
||||
const val MSG_STATE_STOP = 4
|
||||
const val MSG_STATE_STOP_SUCCESS = 41
|
||||
const val MSG_STATE_RESTART = 5
|
||||
|
||||
object EConfigType {
|
||||
|
||||
@JvmField
|
||||
val Vmess = 1
|
||||
|
||||
@JvmField
|
||||
val Shadowsocks = 3
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package com.v2ray.ang.dto
|
||||
|
||||
import cn.hutool.core.codec.Base64
|
||||
import com.google.gson.Gson
|
||||
import com.v2ray.ang.V2RayConfig
|
||||
import com.v2ray.ang.V2RayConfig.SS_PROTOCOL
|
||||
import com.v2ray.ang.V2RayConfig.VMESS_PROTOCOL
|
||||
|
||||
data class AngConfig(
|
||||
var index: Int,
|
||||
var vmess: ArrayList<VmessBean>,
|
||||
var subItem: ArrayList<SubItemBean>
|
||||
) {
|
||||
data class VmessBean(var guid: String = "123456",
|
||||
var address: String = "",
|
||||
var port: Int = 443,
|
||||
var id: String = "",
|
||||
var alterId: Int = 64,
|
||||
var security: String = "auto",
|
||||
var network: String = "tcp",
|
||||
var remarks: String = "",
|
||||
var headerType: String = "none",
|
||||
var requestHost: String = "",
|
||||
var path: String = "",
|
||||
var streamSecurity: String = "",
|
||||
var configType: Int = 1,
|
||||
var configVersion: Int = 2,
|
||||
var testResult: String = "") {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return super.equals(other) || (other is VmessBean &&
|
||||
address == other.address &&
|
||||
port == other.port &&
|
||||
id == other.id &&
|
||||
network == other.network &&
|
||||
headerType == other.headerType &&
|
||||
requestHost == other.requestHost &&
|
||||
path == other.path)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
|
||||
if (configType == V2RayConfig.EConfigType.Vmess) {
|
||||
|
||||
val vmessQRCode = VmessQRCode()
|
||||
|
||||
vmessQRCode.v = configVersion.toString()
|
||||
vmessQRCode.ps = remarks
|
||||
vmessQRCode.add = address
|
||||
vmessQRCode.port = port.toString()
|
||||
vmessQRCode.id = id
|
||||
vmessQRCode.aid = alterId.toString()
|
||||
vmessQRCode.net = network
|
||||
vmessQRCode.type = headerType
|
||||
vmessQRCode.host = requestHost
|
||||
vmessQRCode.path = path
|
||||
vmessQRCode.tls = streamSecurity
|
||||
|
||||
return VMESS_PROTOCOL + cn.hutool.core.codec.Base64.encode(Gson().toJson(vmessQRCode))
|
||||
|
||||
} else if (configType == V2RayConfig.EConfigType.Shadowsocks) {
|
||||
|
||||
val remark = "#" + Base64.encodeUrlSafe(remarks)
|
||||
|
||||
val url = String.format("%s:%s@%s:%s", security, id, address, port)
|
||||
|
||||
return SS_PROTOCOL + Base64.encode(url.toByteArray(charset("UTF-8"))) + remark
|
||||
|
||||
} else {
|
||||
|
||||
error("invalid vmess bean type")
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data class SubItemBean(var id: String = "",
|
||||
var remarks: String = "",
|
||||
var url: String = "")
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
package com.v2ray.ang.dto
|
||||
|
||||
data class V2rayConfig(
|
||||
val stats: Any?=null,
|
||||
val log: LogBean,
|
||||
val policy: PolicyBean,
|
||||
val inbounds: ArrayList<InboundBean>,
|
||||
var outbounds: ArrayList<OutboundBean>,
|
||||
var dns: DnsBean,
|
||||
val routing: RoutingBean) {
|
||||
|
||||
data class LogBean(val access: String,
|
||||
val error: String,
|
||||
val loglevel: String)
|
||||
|
||||
data class InboundBean(
|
||||
var tag: String,
|
||||
var port: Int,
|
||||
var protocol: String,
|
||||
var listen: String?=null,
|
||||
val settings: InSettingsBean,
|
||||
val sniffing: SniffingBean?) {
|
||||
|
||||
data class InSettingsBean(val auth: String? = null,
|
||||
val udp: Boolean? = null,
|
||||
val userLevel: Int? =null,
|
||||
val address: String? = null,
|
||||
val port: Int? = null,
|
||||
val network: String? = null)
|
||||
|
||||
data class SniffingBean(var enabled: Boolean,
|
||||
val destOverride: List<String>)
|
||||
}
|
||||
|
||||
data class OutboundBean(val tag: String,
|
||||
var protocol: String,
|
||||
var settings: OutSettingsBean?,
|
||||
var streamSettings: StreamSettingsBean?,
|
||||
var mux: MuxBean?) {
|
||||
|
||||
data class OutSettingsBean(var vnext: List<VnextBean>?,
|
||||
var servers: List<ServersBean>?,
|
||||
var response: Response) {
|
||||
|
||||
data class VnextBean(var address: String,
|
||||
var port: Int,
|
||||
var users: List<UsersBean>) {
|
||||
|
||||
data class UsersBean(var id: String,
|
||||
var alterId: Int,
|
||||
var security: String,
|
||||
var level: Int)
|
||||
}
|
||||
|
||||
data class ServersBean(var address: String,
|
||||
var method: String,
|
||||
var ota: Boolean,
|
||||
var password: String,
|
||||
var port: Int,
|
||||
var level: Int)
|
||||
|
||||
data class Response(var type: String)
|
||||
}
|
||||
|
||||
data class StreamSettingsBean(var network: String,
|
||||
var security: String,
|
||||
var tcpSettings: TcpsettingsBean?,
|
||||
var kcpsettings: KcpsettingsBean?,
|
||||
var wssettings: WssettingsBean?,
|
||||
var httpsettings: HttpsettingsBean?,
|
||||
var tlssettings: TlssettingsBean?,
|
||||
var quicsettings: QuicsettingBean?
|
||||
) {
|
||||
|
||||
data class TcpsettingsBean(var connectionReuse: Boolean = true,
|
||||
var header: HeaderBean = HeaderBean()) {
|
||||
data class HeaderBean(var type: String = "none",
|
||||
var request: Any? = null,
|
||||
var response: Any? = null)
|
||||
}
|
||||
|
||||
data class KcpsettingsBean(var mtu: Int = 1350,
|
||||
var tti: Int = 20,
|
||||
var uplinkCapacity: Int = 12,
|
||||
var downlinkCapacity: Int = 100,
|
||||
var congestion: Boolean = false,
|
||||
var readBufferSize: Int = 1,
|
||||
var writeBufferSize: Int = 1,
|
||||
var header: HeaderBean = HeaderBean()) {
|
||||
data class HeaderBean(var type: String = "none")
|
||||
}
|
||||
|
||||
data class WssettingsBean(var connectionReuse: Boolean = true,
|
||||
var path: String = "",
|
||||
var headers: HeadersBean = HeadersBean()) {
|
||||
data class HeadersBean(var Host: String = "")
|
||||
}
|
||||
|
||||
data class HttpsettingsBean(var host: List<String> = ArrayList(), var path: String = "")
|
||||
|
||||
data class TlssettingsBean(var allowInsecure: Boolean = true,
|
||||
var serverName: String = "")
|
||||
|
||||
data class QuicsettingBean(var security: String = "none",
|
||||
var key: String = "",
|
||||
var header: HeaderBean = HeaderBean()) {
|
||||
data class HeaderBean(var type: String = "none")
|
||||
}
|
||||
}
|
||||
|
||||
data class MuxBean(var enabled: Boolean)
|
||||
}
|
||||
|
||||
//data class DnsBean(var servers: List<String>)
|
||||
data class DnsBean(var servers: List<Any>?=null,
|
||||
var hosts: Map<String, String>?=null
|
||||
) {
|
||||
data class ServersBean(var address: String = "",
|
||||
var port: Int = 0,
|
||||
var domains: List<String>?)
|
||||
}
|
||||
|
||||
data class RoutingBean(var domainStrategy: String,
|
||||
var rules: ArrayList<RulesBean>) {
|
||||
|
||||
data class RulesBean(var type: String = "",
|
||||
var ip: ArrayList<String>? = null,
|
||||
var domain: ArrayList<String>? = null,
|
||||
var outboundTag: String = "",
|
||||
var port: String? = null,
|
||||
var inboundTag: ArrayList<String>? = null)
|
||||
}
|
||||
|
||||
data class PolicyBean(var levels: Map<String, LevelBean>,
|
||||
var system: Any?=null) {
|
||||
data class LevelBean(
|
||||
var handshake: Int? = null,
|
||||
var connIdle: Int? = null,
|
||||
var uplinkOnly: Int? = null,
|
||||
var downlinkOnly: Int? = null)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package com.v2ray.ang.dto
|
||||
|
||||
data class VmessQRCode(var v: String = "",
|
||||
var ps: String = "",
|
||||
var add: String = "",
|
||||
var port: String = "",
|
||||
var id: String = "",
|
||||
var aid: String = "",
|
||||
var net: String = "",
|
||||
var type: String = "",
|
||||
var host: String = "",
|
||||
var path: String = "",
|
||||
var tls: String = "")
|
|
@ -0,0 +1,172 @@
|
|||
package com.v2ray.ang.util
|
||||
|
||||
import android.text.TextUtils
|
||||
import cn.hutool.core.codec.Base64
|
||||
import org.telegram.messenger.ApplicationLoader
|
||||
import java.io.IOException
|
||||
import java.net.Socket
|
||||
import java.net.URLDecoder
|
||||
import java.net.URLEncoder
|
||||
import java.net.UnknownHostException
|
||||
|
||||
|
||||
object Utils {
|
||||
|
||||
|
||||
/**
|
||||
* parseInt
|
||||
*/
|
||||
fun parseInt(str: String): Int {
|
||||
try {
|
||||
return Integer.parseInt(str)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* is ip address
|
||||
*/
|
||||
fun isIpAddress(value: String): Boolean {
|
||||
try {
|
||||
var addr = value
|
||||
if (addr.isEmpty() || addr.isBlank()) {
|
||||
return false
|
||||
}
|
||||
//CIDR
|
||||
if (addr.indexOf("/") > 0) {
|
||||
val arr = addr.split("/")
|
||||
if (arr.count() == 2 && Integer.parseInt(arr[1]) > 0) {
|
||||
addr = arr[0]
|
||||
}
|
||||
}
|
||||
|
||||
// "::ffff:192.168.173.22"
|
||||
// "[::ffff:192.168.173.22]:80"
|
||||
if (addr.startsWith("::ffff:") && '.' in addr) {
|
||||
addr = addr.drop(7)
|
||||
} else if (addr.startsWith("[::ffff:") && '.' in addr) {
|
||||
addr = addr.drop(8).replace("]", "")
|
||||
}
|
||||
|
||||
// addr = addr.toLowerCase()
|
||||
var octets = addr.split('.').toTypedArray()
|
||||
if (octets.size == 4) {
|
||||
if (octets[3].indexOf(":") > 0) {
|
||||
addr = addr.substring(0, addr.indexOf(":"))
|
||||
}
|
||||
return isIpv4Address(addr)
|
||||
}
|
||||
|
||||
// Ipv6addr [2001:abc::123]:8080
|
||||
return isIpv6Address(addr)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
fun isPureIpAddress(value: String): Boolean {
|
||||
return (isIpv4Address(value) || isIpv6Address(value))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isIpv4Address(value: String): Boolean {
|
||||
val regV4 = Regex("^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$")
|
||||
return regV4.matches(value)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isIpv6Address(value: String): Boolean {
|
||||
var addr = value
|
||||
if (addr.indexOf("[") == 0 && addr.lastIndexOf("]") > 0) {
|
||||
addr = addr.drop(1)
|
||||
addr = addr.dropLast(addr.count() - addr.lastIndexOf("]"))
|
||||
}
|
||||
val regV6 = Regex("^((?:[0-9A-Fa-f]{1,4}))?((?::[0-9A-Fa-f]{1,4}))*::((?:[0-9A-Fa-f]{1,4}))?((?::[0-9A-Fa-f]{1,4}))*|((?:[0-9A-Fa-f]{1,4}))((?::[0-9A-Fa-f]{1,4})){7}$")
|
||||
return regV6.matches(addr)
|
||||
}
|
||||
|
||||
fun urlDecode(url: String): String {
|
||||
try {
|
||||
return URLDecoder.decode(url, "UTF-8")
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
fun urlEncode(url: String): String {
|
||||
try {
|
||||
return URLEncoder.encode(url, "UTF-8")
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* readTextFromAssets
|
||||
*/
|
||||
fun readTextFromAssets(fileName: String): String {
|
||||
val content = ApplicationLoader.applicationContext.assets.open(fileName).bufferedReader().use {
|
||||
it.readText()
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
/**
|
||||
* ping
|
||||
*/
|
||||
fun ping(url: String): String {
|
||||
try {
|
||||
val command = "/system/bin/ping -c 3 $url"
|
||||
val process = Runtime.getRuntime().exec(command)
|
||||
val allText = process.inputStream.bufferedReader().use { it.readText() }
|
||||
if (!TextUtils.isEmpty(allText)) {
|
||||
val tempInfo = allText.substring(allText.indexOf("min/avg/max/mdev") + 19)
|
||||
val temps = tempInfo.split("/".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray()
|
||||
if (temps.count() > 0 && temps[0].length < 10) {
|
||||
return temps[0].toFloat().toInt().toString() + "ms"
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return "-1ms"
|
||||
}
|
||||
|
||||
/**
|
||||
* tcping
|
||||
*/
|
||||
fun tcping(url: String, port: Int): String {
|
||||
var time = -1L
|
||||
for (k in 0 until 2) {
|
||||
val one = socketConnectTime(url, port)
|
||||
if (one != -1L)
|
||||
if (time == -1L || one < time) {
|
||||
time = one
|
||||
}
|
||||
}
|
||||
return time.toString() + "ms"
|
||||
}
|
||||
|
||||
fun socketConnectTime(url: String, port: Int): Long {
|
||||
try {
|
||||
val start = System.currentTimeMillis()
|
||||
val socket = Socket(url, port)
|
||||
val time = System.currentTimeMillis() - start
|
||||
socket.close()
|
||||
return time
|
||||
} catch (e: UnknownHostException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,371 @@
|
|||
package com.v2ray.ang.util
|
||||
|
||||
import android.text.TextUtils
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonObject
|
||||
import com.v2ray.ang.V2RayConfig
|
||||
import com.v2ray.ang.dto.AngConfig.VmessBean
|
||||
import com.v2ray.ang.dto.V2rayConfig
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
||||
object V2rayConfigUtil {
|
||||
private val requestObj: JsonObject by lazy {
|
||||
Gson().fromJson("""{"version":"1.1","method":"GET","path":["/"],"headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36","Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46"],"Accept-Encoding":["gzip, deflate"],"Connection":["keep-alive"],"Pragma":"no-cache"}}""", JsonObject::class.java)
|
||||
}
|
||||
|
||||
// private val responseObj: JSONObject by lazy {
|
||||
// JSONObject("""{"version":"1.1","status":"200","reason":"OK","headers":{"Content-Type":["application/octet-stream","video/mpeg"],"Transfer-Encoding":["chunked"],"Connection":["keep-alive"],"Pragma":"no-cache"}}""")
|
||||
// }
|
||||
|
||||
data class Result(var status: Boolean, var content: String)
|
||||
|
||||
@JvmStatic
|
||||
var currDomain: String = ""
|
||||
|
||||
/**
|
||||
* 生成v2ray的客户端配置文件
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getV2rayConfig(vmess: VmessBean, port: Int): Result {
|
||||
val result = Result(false, "")
|
||||
try {
|
||||
//取得默认配置
|
||||
val assets = Utils.readTextFromAssets("v2ray_config.json")
|
||||
if (TextUtils.isEmpty(assets)) {
|
||||
return result
|
||||
}
|
||||
|
||||
//转成Json
|
||||
val v2rayConfig = Gson().fromJson(assets, V2rayConfig::class.java) ?: return result
|
||||
// if (v2rayConfig == null) {
|
||||
// return result
|
||||
// }
|
||||
|
||||
inbounds(vmess, v2rayConfig, port)
|
||||
|
||||
outbounds(vmess, v2rayConfig)
|
||||
|
||||
routing(vmess, v2rayConfig)
|
||||
|
||||
val finalConfig = GsonBuilder().setPrettyPrinting().create().toJson(v2rayConfig)
|
||||
|
||||
result.status = true
|
||||
result.content = finalConfig
|
||||
return result
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private fun inbounds(vmess: VmessBean, v2rayConfig: V2rayConfig, port: Int): Boolean {
|
||||
try {
|
||||
v2rayConfig.inbounds.forEach { curInbound ->
|
||||
curInbound.listen = "127.0.0.1"
|
||||
}
|
||||
v2rayConfig.inbounds[0].port = port
|
||||
// val socksPort = Utils.parseInt(app.defaultDPreference.getPrefString(SettingsActivity.PREF_SOCKS_PORT, "10808"))
|
||||
// val lanconnPort = Utils.parseInt(app.defaultDPreference.getPrefString(SettingsActivity.PREF_HTTP_PORT, ""))
|
||||
|
||||
// if (socksPort > 0) {
|
||||
// v2rayConfig.inbounds[0].port = socksPort
|
||||
// }
|
||||
// if (lanconnPort > 0) {
|
||||
// val httpCopy = v2rayConfig.inbounds[0].copy()
|
||||
// httpCopy.port = lanconnPort
|
||||
// httpCopy.protocol = "http"
|
||||
// v2rayConfig.inbounds.add(httpCopy)
|
||||
// }
|
||||
v2rayConfig.inbounds[0].sniffing?.enabled = false
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* vmess协议服务器配置
|
||||
*/
|
||||
private fun outbounds(vmess: VmessBean, v2rayConfig: V2rayConfig): Boolean {
|
||||
try {
|
||||
val outbound = v2rayConfig.outbounds[0]
|
||||
|
||||
when (vmess.configType) {
|
||||
V2RayConfig.EConfigType.Vmess -> {
|
||||
outbound.settings?.servers = null
|
||||
|
||||
val vnext = v2rayConfig.outbounds[0].settings?.vnext?.get(0)
|
||||
vnext?.address = vmess.address
|
||||
vnext?.port = vmess.port
|
||||
val user = vnext?.users?.get(0)
|
||||
user?.id = vmess.id
|
||||
user?.alterId = vmess.alterId
|
||||
user?.security = vmess.security
|
||||
user?.level = 8
|
||||
|
||||
//Mux
|
||||
val muxEnabled = false//app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_MUX_ENABLED, false)
|
||||
outbound.mux?.enabled = muxEnabled
|
||||
|
||||
//远程服务器底层传输配置
|
||||
outbound.streamSettings = boundStreamSettings(vmess)
|
||||
|
||||
outbound.protocol = "vmess"
|
||||
}
|
||||
V2RayConfig.EConfigType.Shadowsocks -> {
|
||||
outbound.settings?.vnext = null
|
||||
|
||||
val server = outbound.settings?.servers?.get(0)
|
||||
server?.address = vmess.address
|
||||
server?.method = vmess.security
|
||||
server?.ota = false
|
||||
server?.password = vmess.id
|
||||
server?.port = vmess.port
|
||||
server?.level = 8
|
||||
|
||||
//Mux
|
||||
outbound.mux?.enabled = false
|
||||
|
||||
outbound.protocol = "shadowsocks"
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
|
||||
var serverDomain: String
|
||||
if (Utils.isIpv6Address(vmess.address)) {
|
||||
serverDomain = String.format("[%s]:%s", vmess.address, vmess.port)
|
||||
} else {
|
||||
serverDomain = String.format("%s:%s", vmess.address, vmess.port)
|
||||
}
|
||||
currDomain = serverDomain
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 远程服务器底层传输配置
|
||||
*/
|
||||
private fun boundStreamSettings(vmess: VmessBean): V2rayConfig.OutboundBean.StreamSettingsBean {
|
||||
val streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean("", "", null, null, null, null, null, null)
|
||||
try {
|
||||
//远程服务器底层传输配置
|
||||
streamSettings.network = vmess.network
|
||||
streamSettings.security = vmess.streamSecurity
|
||||
|
||||
//streamSettings
|
||||
when (streamSettings.network) {
|
||||
"kcp" -> {
|
||||
val kcpsettings = V2rayConfig.OutboundBean.StreamSettingsBean.KcpsettingsBean()
|
||||
kcpsettings.mtu = 1350
|
||||
kcpsettings.tti = 50
|
||||
kcpsettings.uplinkCapacity = 12
|
||||
kcpsettings.downlinkCapacity = 100
|
||||
kcpsettings.congestion = false
|
||||
kcpsettings.readBufferSize = 1
|
||||
kcpsettings.writeBufferSize = 1
|
||||
kcpsettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.KcpsettingsBean.HeaderBean()
|
||||
kcpsettings.header.type = vmess.headerType
|
||||
streamSettings.kcpsettings = kcpsettings
|
||||
}
|
||||
"ws" -> {
|
||||
val wssettings = V2rayConfig.OutboundBean.StreamSettingsBean.WssettingsBean()
|
||||
wssettings.connectionReuse = true
|
||||
val host = vmess.requestHost.trim()
|
||||
val path = vmess.path.trim()
|
||||
|
||||
if (!TextUtils.isEmpty(host)) {
|
||||
wssettings.headers = V2rayConfig.OutboundBean.StreamSettingsBean.WssettingsBean.HeadersBean()
|
||||
wssettings.headers.Host = host
|
||||
}
|
||||
if (!TextUtils.isEmpty(path)) {
|
||||
wssettings.path = path
|
||||
}
|
||||
streamSettings.wssettings = wssettings
|
||||
|
||||
val tlssettings = V2rayConfig.OutboundBean.StreamSettingsBean.TlssettingsBean()
|
||||
tlssettings.allowInsecure = true
|
||||
if (!TextUtils.isEmpty(host)) {
|
||||
tlssettings.serverName = host
|
||||
}
|
||||
streamSettings.tlssettings = tlssettings
|
||||
}
|
||||
"h2" -> {
|
||||
val httpsettings = V2rayConfig.OutboundBean.StreamSettingsBean.HttpsettingsBean()
|
||||
val host = vmess.requestHost.trim()
|
||||
val path = vmess.path.trim()
|
||||
|
||||
if (!TextUtils.isEmpty(host)) {
|
||||
httpsettings.host = host.split(",").map { it.trim() }
|
||||
}
|
||||
httpsettings.path = path
|
||||
streamSettings.httpsettings = httpsettings
|
||||
|
||||
val tlssettings = V2rayConfig.OutboundBean.StreamSettingsBean.TlssettingsBean()
|
||||
tlssettings.allowInsecure = true
|
||||
streamSettings.tlssettings = tlssettings
|
||||
}
|
||||
"quic" -> {
|
||||
val quicsettings = V2rayConfig.OutboundBean.StreamSettingsBean.QuicsettingBean()
|
||||
val host = vmess.requestHost.trim()
|
||||
val path = vmess.path.trim()
|
||||
|
||||
quicsettings.security = host
|
||||
quicsettings.key = path
|
||||
|
||||
quicsettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.QuicsettingBean.HeaderBean()
|
||||
quicsettings.header.type = vmess.headerType
|
||||
|
||||
streamSettings.quicsettings = quicsettings
|
||||
}
|
||||
else -> {
|
||||
//tcp带http伪装
|
||||
if (vmess.headerType == "http") {
|
||||
val tcpSettings = V2rayConfig.OutboundBean.StreamSettingsBean.TcpsettingsBean()
|
||||
tcpSettings.connectionReuse = true
|
||||
tcpSettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.TcpsettingsBean.HeaderBean()
|
||||
tcpSettings.header.type = vmess.headerType
|
||||
|
||||
// if (requestObj.has("headers")
|
||||
// || requestObj.optJSONObject("headers").has("Pragma")) {
|
||||
// val arrHost = ArrayList<String>()
|
||||
// vmess.requestHost
|
||||
// .split(",")
|
||||
// .forEach {
|
||||
// arrHost.add(it)
|
||||
// }
|
||||
// requestObj.optJSONObject("headers")
|
||||
// .put("Host", arrHost)
|
||||
//
|
||||
// }
|
||||
if (!TextUtils.isEmpty(vmess.requestHost)) {
|
||||
val arrHost = ArrayList<String>()
|
||||
vmess.requestHost
|
||||
.split(",")
|
||||
.forEach {
|
||||
arrHost.add("\"$it\"")
|
||||
}
|
||||
requestObj.getAsJsonObject("headers")
|
||||
.add("Host", Gson().fromJson(arrHost.toString(), JsonArray::class.java))
|
||||
}
|
||||
if (!TextUtils.isEmpty(vmess.path)) {
|
||||
val arrPath = ArrayList<String>()
|
||||
vmess.path
|
||||
.split(",")
|
||||
.forEach {
|
||||
arrPath.add("\"$it\"")
|
||||
}
|
||||
requestObj.add("path", Gson().fromJson(arrPath.toString(), JsonArray::class.java))
|
||||
}
|
||||
tcpSettings.header.request = requestObj
|
||||
//tcpSettings.header.response = responseObj
|
||||
streamSettings.tcpSettings = tcpSettings
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return streamSettings
|
||||
}
|
||||
return streamSettings
|
||||
}
|
||||
|
||||
/**
|
||||
* routing
|
||||
*/
|
||||
private fun routing(vmess: VmessBean, v2rayConfig: V2rayConfig) {
|
||||
|
||||
v2rayConfig.routing.domainStrategy = "IPIfNonMatch"
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* is valid config
|
||||
*/
|
||||
fun isValidConfig(conf: String): Boolean {
|
||||
try {
|
||||
val jObj = JSONObject(conf)
|
||||
var hasBound = false
|
||||
//hasBound = (jObj.has("outbounds") and jObj.has("inbounds")) or (jObj.has("outbound") and jObj.has("inbound"))
|
||||
hasBound = (jObj.has("outbounds")) or (jObj.has("outbound"))
|
||||
return hasBound
|
||||
} catch (e: JSONException) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseDomainName(jsonConfig: String): String {
|
||||
try {
|
||||
val jObj = JSONObject(jsonConfig)
|
||||
var domainName: String
|
||||
if (jObj.has("outbound")) {
|
||||
domainName = parseDomainName(jObj.optJSONObject("outbound"))
|
||||
if (!TextUtils.isEmpty(domainName)) {
|
||||
return domainName
|
||||
}
|
||||
}
|
||||
if (jObj.has("outbounds")) {
|
||||
for (i in 0..(jObj.optJSONArray("outbounds").length() - 1)) {
|
||||
domainName = parseDomainName(jObj.optJSONArray("outbounds").getJSONObject(i))
|
||||
if (!TextUtils.isEmpty(domainName)) {
|
||||
return domainName
|
||||
}
|
||||
}
|
||||
}
|
||||
if (jObj.has("outboundDetour")) {
|
||||
for (i in 0..(jObj.optJSONArray("outboundDetour").length() - 1)) {
|
||||
domainName = parseDomainName(jObj.optJSONArray("outboundDetour").getJSONObject(i))
|
||||
if (!TextUtils.isEmpty(domainName)) {
|
||||
return domainName
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
private fun parseDomainName(outbound: JSONObject): String {
|
||||
try {
|
||||
if (outbound.has("settings")) {
|
||||
var vnext: JSONArray?
|
||||
if (outbound.optJSONObject("settings").has("vnext")) {
|
||||
// vmess
|
||||
vnext = outbound.optJSONObject("settings").optJSONArray("vnext")
|
||||
} else if (outbound.optJSONObject("settings").has("servers")) {
|
||||
// shadowsocks or socks
|
||||
vnext = outbound.optJSONObject("settings").optJSONArray("servers")
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
for (i in 0..(vnext.length() - 1)) {
|
||||
val item = vnext.getJSONObject(i)
|
||||
val address = item.getString("address")
|
||||
val port = item.getString("port")
|
||||
if (Utils.isIpv6Address(address)) {
|
||||
return String.format("[%s]:%s", address, port)
|
||||
} else {
|
||||
return String.format("%s:%s", address, port)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -156,12 +156,15 @@ public class LinkifyPort {
|
|||
private static final String DOMAIN_NAME_STR = "(" + HOST_NAME + "|" + IP_ADDRESS_STRING + ")";
|
||||
private static final Pattern DOMAIN_NAME = Pattern.compile(DOMAIN_NAME_STR);
|
||||
private static final String PROTOCOL = "(?i:http|https|ton|tg)://";
|
||||
private static final String PROXY_PROTOCOL = "(?i:vmess|vmess1|ss|ssr|rb)://";
|
||||
private static final String WORD_BOUNDARY = "(?:\\b|$|^)";
|
||||
private static final String USER_INFO = "(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
|
||||
+ "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
|
||||
+ "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@";
|
||||
private static final String PORT_NUMBER = "\\:\\d{1,5}";
|
||||
private static final String PATH_AND_QUERY = "[/\\?](?:(?:[" + LABEL_CHAR + ";/\\?:@&=#~" + "\\-\\.\\+!\\*'\\(\\),_\\$])|(?:%[a-fA-F0-9]{2}))*";
|
||||
private static final String BASE64 = "(?:[A-Za-z0-9+\\/]{4}\\\\n?)*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=)";
|
||||
private static final String PATH_AND_QUERY_BASE64 = "[/\\?]?(?:(?:[" + LABEL_CHAR + ";/\\?:@&=#~" + "\\-\\.\\+!\\*'\\(\\),_\\$])|(?:%[a-fA-F0-9]{2})|" + BASE64 + ")*";
|
||||
private static final String RELAXED_DOMAIN_NAME = "(?:" + "(?:" + IRI_LABEL + "(?:\\.(?=\\S))" + "?)+" + "|" + IP_ADDRESS_STRING + ")";
|
||||
|
||||
private static final String WEB_URL_WITHOUT_PROTOCOL = "("
|
||||
|
@ -185,7 +188,20 @@ public class LinkifyPort {
|
|||
+ "(?:" + PATH_AND_QUERY + ")?"
|
||||
+ WORD_BOUNDARY
|
||||
+ ")";
|
||||
|
||||
private static final String PROXY_URL = "("
|
||||
+ WORD_BOUNDARY
|
||||
+ "(?:"
|
||||
+ "(?:" + PROXY_PROTOCOL + "(?:" + USER_INFO + ")?" + ")"
|
||||
+ "(?:" + RELAXED_DOMAIN_NAME + ")?"
|
||||
+ "(?:" + PORT_NUMBER + ")?"
|
||||
+ ")"
|
||||
+ "(?:" + PATH_AND_QUERY_BASE64 + ")?"
|
||||
+ WORD_BOUNDARY
|
||||
+ ")";
|
||||
|
||||
public static Pattern WEB_URL = null;
|
||||
public static Pattern PROXY_PATTERN = Pattern.compile(PROXY_URL);
|
||||
|
||||
static {
|
||||
try {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -348,7 +348,7 @@ public class Browser {
|
|||
|
||||
}
|
||||
return true;
|
||||
} else if ("tg".equals(uri.getScheme())) {
|
||||
} else if ("tg".equals(uri.getScheme()) || "vmess".equals(uri.getScheme()) || "vmesss1".equals(uri.getScheme()) || "ss".equals(uri.getScheme()) || "ssr".equals(uri.getScheme())) {
|
||||
return true;
|
||||
} else if ("telegram.dog".equals(host)) {
|
||||
String path = uri.getPath();
|
||||
|
|
|
@ -64,7 +64,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
|||
import androidx.recyclerview.widget.LinearSmoothScroller;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import tw.nekomimi.nekogram.NekoConfig;
|
||||
import tw.nekomimi.nekogram.utils.EnvUtil;
|
||||
|
||||
public class ChatAttachAlertDocumentLayout extends ChatAttachAlert.AttachAlertLayout {
|
||||
|
||||
|
@ -109,6 +109,12 @@ public class ChatAttachAlertDocumentLayout extends ChatAttachAlert.AttachAlertLa
|
|||
private boolean canSelectOnlyImageFiles;
|
||||
private boolean allowMusic;
|
||||
|
||||
public void setAllowPhoto(boolean allowPhoto) {
|
||||
this.allowPhoto = allowPhoto;
|
||||
}
|
||||
|
||||
private boolean allowPhoto = true;
|
||||
|
||||
private boolean searching;
|
||||
|
||||
private boolean sortByName;
|
||||
|
@ -222,7 +228,7 @@ public class ChatAttachAlertDocumentLayout extends ChatAttachAlert.AttachAlertLa
|
|||
|
||||
emptyImageView = new ImageView(context);
|
||||
emptyImageView.setImageResource(R.drawable.files_empty);
|
||||
emptyImageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_dialogEmptyImage), PorterDuff.Mode.MULTIPLY));
|
||||
emptyImageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_dialogEmptyImage), PorterDuff.Mode.SRC_IN));
|
||||
emptyView.addView(emptyImageView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT));
|
||||
|
||||
emptyTitleTextView = new TextView(context);
|
||||
|
@ -969,12 +975,7 @@ public class ChatAttachAlertDocumentLayout extends ChatAttachAlert.AttachAlertLa
|
|||
|
||||
ListItem fs;
|
||||
try {
|
||||
File telegramPath;
|
||||
if (NekoConfig.saveCacheToPrivateDirectory) {
|
||||
telegramPath = new File(ApplicationLoader.applicationContext.getFilesDir(), "Telegram");
|
||||
} else {
|
||||
telegramPath = new File(Environment.getExternalStorageDirectory(), "Telegram");
|
||||
}
|
||||
File telegramPath = EnvUtil.getTelegramPath();
|
||||
if (telegramPath.exists()) {
|
||||
fs = new ListItem();
|
||||
fs.title = "Telegram";
|
||||
|
@ -987,12 +988,16 @@ public class ChatAttachAlertDocumentLayout extends ChatAttachAlert.AttachAlertLa
|
|||
FileLog.e(e);
|
||||
}
|
||||
|
||||
fs = new ListItem();
|
||||
fs.title = LocaleController.getString("Gallery", R.string.Gallery);
|
||||
fs.subtitle = LocaleController.getString("GalleryInfo", R.string.GalleryInfo);
|
||||
fs.icon = R.drawable.files_gallery;
|
||||
fs.file = null;
|
||||
items.add(fs);
|
||||
if (allowPhoto) {
|
||||
|
||||
fs = new ListItem();
|
||||
fs.title = LocaleController.getString("Gallery", R.string.Gallery);
|
||||
fs.subtitle = LocaleController.getString("GalleryInfo", R.string.GalleryInfo);
|
||||
fs.icon = R.drawable.files_gallery;
|
||||
fs.file = null;
|
||||
items.add(fs);
|
||||
|
||||
}
|
||||
|
||||
if (allowMusic) {
|
||||
fs = new ListItem();
|
||||
|
|
|
@ -26,6 +26,8 @@ public class PickerBottomLayout extends FrameLayout {
|
|||
|
||||
public LinearLayout doneButton;
|
||||
public TextView cancelButton;
|
||||
public LinearLayout middleButton;
|
||||
public TextView middleButtonTextView;
|
||||
public TextView doneButtonTextView;
|
||||
public TextView doneButtonBadgeTextView;
|
||||
|
||||
|
@ -48,11 +50,31 @@ public class PickerBottomLayout extends FrameLayout {
|
|||
cancelButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
|
||||
addView(cancelButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT));
|
||||
|
||||
LinearLayout rightLayout = new LinearLayout(context);
|
||||
rightLayout.setOrientation(LinearLayout.HORIZONTAL);
|
||||
rightLayout.setGravity(Gravity.CENTER_VERTICAL);
|
||||
addView(rightLayout, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.RIGHT));
|
||||
|
||||
middleButton = new LinearLayout(context);
|
||||
middleButton.setOrientation(LinearLayout.HORIZONTAL);
|
||||
middleButton.setBackgroundDrawable(Theme.createSelectorDrawable(0x0f000000, 0));
|
||||
middleButton.setPadding(AndroidUtilities.dp(33), 0, AndroidUtilities.dp(33), 0);
|
||||
middleButton.setVisibility(GONE);
|
||||
rightLayout.addView(middleButton);
|
||||
|
||||
middleButtonTextView = new TextView(context);
|
||||
middleButtonTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
|
||||
middleButtonTextView.setTextColor(Theme.getColor(Theme.key_picker_enabledButton));
|
||||
middleButtonTextView.setGravity(Gravity.CENTER);
|
||||
middleButtonTextView.setCompoundDrawablePadding(AndroidUtilities.dp(8));
|
||||
middleButtonTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
|
||||
middleButton.addView(middleButtonTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL));
|
||||
|
||||
doneButton = new LinearLayout(context);
|
||||
doneButton.setOrientation(LinearLayout.HORIZONTAL);
|
||||
doneButton.setBackgroundDrawable(Theme.createSelectorDrawable(0x0f000000, 0));
|
||||
doneButton.setPadding(AndroidUtilities.dp(33), 0, AndroidUtilities.dp(33), 0);
|
||||
addView(doneButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.RIGHT));
|
||||
rightLayout.addView(doneButton);
|
||||
|
||||
doneButtonBadgeTextView = new TextView(context);
|
||||
doneButtonBadgeTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
|
||||
|
|
|
@ -45,6 +45,8 @@ import com.google.android.exoplayer2.C;
|
|||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.json.JSONTokener;
|
||||
|
@ -59,25 +61,19 @@ import org.telegram.messenger.ImageReceiver;
|
|||
import org.telegram.messenger.R;
|
||||
import org.telegram.messenger.Utilities;
|
||||
import org.telegram.tgnet.TLRPC;
|
||||
import tw.nekomimi.nekogram.utils.HttpUtil;
|
||||
import tw.nekomimi.nekogram.utils.ThreadUtil;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerDelegate, AudioManager.OnAudioFocusChangeListener {
|
||||
|
||||
|
@ -445,139 +441,48 @@ public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerD
|
|||
}
|
||||
|
||||
protected String downloadUrlContent(AsyncTask parentTask, String url, HashMap<String, String> headers, boolean tryGzip) {
|
||||
boolean canRetry = true;
|
||||
InputStream httpConnectionStream = null;
|
||||
boolean done = false;
|
||||
StringBuilder result = null;
|
||||
URLConnection httpConnection = null;
|
||||
try {
|
||||
URL downloadUrl = new URL(url);
|
||||
httpConnection = downloadUrl.openConnection();
|
||||
httpConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20150101 Firefox/47.0 (Chrome)");
|
||||
if (tryGzip) {
|
||||
httpConnection.addRequestProperty("Accept-Encoding", "gzip, deflate");
|
||||
}
|
||||
httpConnection.addRequestProperty("Accept-Language", "en-us,en;q=0.5");
|
||||
httpConnection.addRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
|
||||
httpConnection.addRequestProperty("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7");
|
||||
if (headers != null) {
|
||||
for (HashMap.Entry<String, String> entry : headers.entrySet()) {
|
||||
httpConnection.addRequestProperty(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
httpConnection.setConnectTimeout(5000);
|
||||
httpConnection.setReadTimeout(5000);
|
||||
if (httpConnection instanceof HttpURLConnection) {
|
||||
HttpURLConnection httpURLConnection = (HttpURLConnection) httpConnection;
|
||||
httpURLConnection.setInstanceFollowRedirects(true);
|
||||
int status = httpURLConnection.getResponseCode();
|
||||
if (status == HttpURLConnection.HTTP_MOVED_TEMP || status == HttpURLConnection.HTTP_MOVED_PERM || status == HttpURLConnection.HTTP_SEE_OTHER) {
|
||||
String newUrl = httpURLConnection.getHeaderField("Location");
|
||||
String cookies = httpURLConnection.getHeaderField("Set-Cookie");
|
||||
downloadUrl = new URL(newUrl);
|
||||
httpConnection = downloadUrl.openConnection();
|
||||
httpConnection.setRequestProperty("Cookie", cookies);
|
||||
httpConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20150101 Firefox/47.0 (Chrome)");
|
||||
if (tryGzip) {
|
||||
httpConnection.addRequestProperty("Accept-Encoding", "gzip, deflate");
|
||||
}
|
||||
httpConnection.addRequestProperty("Accept-Language", "en-us,en;q=0.5");
|
||||
httpConnection.addRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
|
||||
httpConnection.addRequestProperty("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7");
|
||||
if (headers != null) {
|
||||
for (HashMap.Entry<String, String> entry : headers.entrySet()) {
|
||||
httpConnection.addRequestProperty(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
httpConnection.connect();
|
||||
if (tryGzip) {
|
||||
try {
|
||||
httpConnectionStream = new GZIPInputStream(httpConnection.getInputStream());
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
if (httpConnectionStream != null) {
|
||||
httpConnectionStream.close();
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
|
||||
}
|
||||
httpConnection = downloadUrl.openConnection();
|
||||
httpConnection.connect();
|
||||
httpConnectionStream = httpConnection.getInputStream();
|
||||
}
|
||||
} else {
|
||||
httpConnectionStream = httpConnection.getInputStream();
|
||||
OkHttpClient client = HttpUtil.getOkHttpClientWithCurrProxy().newBuilder()
|
||||
.followRedirects(true)
|
||||
.followSslRedirects(true)
|
||||
.build();
|
||||
|
||||
Request.Builder request = new Request.Builder().url(url)
|
||||
.header("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20150101 Firefox/47.0 (Chrome)")
|
||||
.header("Accept-Language", "en-us,en;q=0.5")
|
||||
.header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
|
||||
.header("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7");
|
||||
|
||||
if (headers != null) {
|
||||
|
||||
for (Map.Entry<String, String> header : headers.entrySet()) {
|
||||
|
||||
request.addHeader(header.getKey(), header.getValue());
|
||||
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
if (e instanceof SocketTimeoutException) {
|
||||
if (ApplicationLoader.isNetworkOnline()) {
|
||||
canRetry = false;
|
||||
}
|
||||
} else if (e instanceof UnknownHostException) {
|
||||
canRetry = false;
|
||||
} else if (e instanceof SocketException) {
|
||||
if (e.getMessage() != null && e.getMessage().contains("ECONNRESET")) {
|
||||
canRetry = false;
|
||||
}
|
||||
} else if (e instanceof FileNotFoundException) {
|
||||
canRetry = false;
|
||||
}
|
||||
FileLog.e(e);
|
||||
|
||||
}
|
||||
|
||||
if (canRetry) {
|
||||
if (tryGzip) {
|
||||
|
||||
request.addHeader("Accept-Encoding", "gzip, deflate");
|
||||
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
|
||||
do {
|
||||
try {
|
||||
if (httpConnection instanceof HttpURLConnection) {
|
||||
int code = ((HttpURLConnection) httpConnection).getResponseCode();
|
||||
if (code != HttpURLConnection.HTTP_OK && code != HttpURLConnection.HTTP_ACCEPTED && code != HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||
//canRetry = false;
|
||||
}
|
||||
}
|
||||
return client.newCall(request.build()).execute().body().string();
|
||||
} catch (Exception e) {
|
||||
FileLog.e(e);
|
||||
}
|
||||
count ++;
|
||||
ThreadUtil.sleep(1000);
|
||||
} while (count < 3);
|
||||
|
||||
if (httpConnectionStream != null) {
|
||||
try {
|
||||
byte[] data = new byte[1024 * 32];
|
||||
while (true) {
|
||||
if (parentTask.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
try {
|
||||
int read = httpConnectionStream.read(data);
|
||||
if (read > 0) {
|
||||
if (result == null) {
|
||||
result = new StringBuilder();
|
||||
}
|
||||
result.append(new String(data, 0, read, StandardCharsets.UTF_8));
|
||||
} else if (read == -1) {
|
||||
done = true;
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
FileLog.e(e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
try {
|
||||
if (httpConnectionStream != null) {
|
||||
httpConnectionStream.close();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
FileLog.e(e);
|
||||
}
|
||||
}
|
||||
return done ? result.toString() : null;
|
||||
}
|
||||
|
||||
private class YoutubeVideoTask extends AsyncTask<Void, Void, String[]> {
|
||||
|
|
|
@ -15,12 +15,11 @@ import android.app.Activity;
|
|||
import android.app.ActivityManager;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Shader;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
|
@ -50,43 +49,50 @@ import android.widget.LinearLayout;
|
|||
import android.widget.RelativeLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.v2ray.ang.V2RayConfig;
|
||||
|
||||
import org.telegram.messenger.AccountInstance;
|
||||
import org.telegram.messenger.AndroidUtilities;
|
||||
import org.telegram.messenger.ApplicationLoader;
|
||||
import org.telegram.messenger.BuildVars;
|
||||
import org.telegram.messenger.ChatObject;
|
||||
import org.telegram.messenger.ContactsController;
|
||||
import org.telegram.messenger.LocationController;
|
||||
import org.telegram.messenger.MediaDataController;
|
||||
import org.telegram.messenger.FileLoader;
|
||||
import org.telegram.messenger.FileLog;
|
||||
import org.telegram.messenger.ImageLoader;
|
||||
import org.telegram.messenger.LocaleController;
|
||||
import org.telegram.messenger.MediaController;
|
||||
import org.telegram.messenger.MediaDataController;
|
||||
import org.telegram.messenger.MessageObject;
|
||||
import org.telegram.messenger.MessagesController;
|
||||
import org.telegram.messenger.MessagesStorage;
|
||||
import org.telegram.messenger.SendMessagesHelper;
|
||||
import org.telegram.messenger.SharedConfig;
|
||||
import org.telegram.messenger.UserObject;
|
||||
import org.telegram.messenger.Utilities;
|
||||
import org.telegram.messenger.ApplicationLoader;
|
||||
import org.telegram.messenger.FileLog;
|
||||
import org.telegram.messenger.LocaleController;
|
||||
import org.telegram.messenger.NotificationCenter;
|
||||
import org.telegram.messenger.R;
|
||||
import org.telegram.messenger.SendMessagesHelper;
|
||||
import org.telegram.messenger.SharedConfig;
|
||||
import org.telegram.messenger.UserConfig;
|
||||
import org.telegram.messenger.UserObject;
|
||||
import org.telegram.messenger.Utilities;
|
||||
import org.telegram.messenger.browser.Browser;
|
||||
import org.telegram.messenger.camera.CameraController;
|
||||
import org.telegram.tgnet.ConnectionsManager;
|
||||
import org.telegram.tgnet.TLRPC;
|
||||
import org.telegram.messenger.UserConfig;
|
||||
import org.telegram.ui.ActionBar.AlertDialog;
|
||||
import org.telegram.ui.Adapters.DrawerLayoutAdapter;
|
||||
import org.telegram.ui.ActionBar.ActionBarLayout;
|
||||
import org.telegram.ui.ActionBar.AlertDialog;
|
||||
import org.telegram.ui.ActionBar.BaseFragment;
|
||||
import org.telegram.ui.ActionBar.DrawerLayoutContainer;
|
||||
import org.telegram.ui.ActionBar.Theme;
|
||||
import org.telegram.ui.Adapters.DrawerLayoutAdapter;
|
||||
import org.telegram.ui.Cells.DrawerActionCheckCell;
|
||||
import org.telegram.ui.Cells.DrawerAddCell;
|
||||
import org.telegram.ui.Cells.DrawerUserCell;
|
||||
import org.telegram.ui.Cells.LanguageCell;
|
||||
import org.telegram.ui.Components.AudioPlayerAlert;
|
||||
import org.telegram.ui.Components.AlertsCreator;
|
||||
import org.telegram.ui.Components.AudioPlayerAlert;
|
||||
import org.telegram.ui.Components.BlockingUpdateView;
|
||||
import org.telegram.ui.Components.ChatActivityEnterView;
|
||||
import org.telegram.ui.Components.Easings;
|
||||
|
@ -100,10 +106,9 @@ import org.telegram.ui.Components.RecyclerListView;
|
|||
import org.telegram.ui.Components.SharingLocationsAlert;
|
||||
import org.telegram.ui.Components.SideMenultItemAnimator;
|
||||
import org.telegram.ui.Components.StickersAlert;
|
||||
import org.telegram.ui.ActionBar.Theme;
|
||||
import org.telegram.ui.Components.Switch;
|
||||
import org.telegram.ui.Components.TermsOfServiceView;
|
||||
import org.telegram.ui.Components.ThemeEditorView;
|
||||
import org.telegram.ui.Components.UpdateAppAlertDialog;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
|
@ -113,11 +118,17 @@ import java.util.Map;
|
|||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import tw.nekomimi.nekogram.NekoConfig;
|
||||
import tw.nekomimi.nekogram.NekoXConfig;
|
||||
import tw.nekomimi.nekogram.NekoXSettingActivity;
|
||||
import tw.nekomimi.nekogram.settings.NekoSettingsActivity;
|
||||
import tw.nekomimi.nekogram.sub.SubInfo;
|
||||
import tw.nekomimi.nekogram.sub.SubManager;
|
||||
import tw.nekomimi.nekogram.utils.AlertUtil;
|
||||
import tw.nekomimi.nekogram.utils.PrivacyUtil;
|
||||
import tw.nekomimi.nekogram.utils.ProxyUtil;
|
||||
import tw.nekomimi.nekogram.utils.UIUtil;
|
||||
import tw.nekomimi.nekogram.utils.UpdateUtil;
|
||||
|
||||
public class LaunchActivity extends Activity implements ActionBarLayout.ActionBarLayoutDelegate, NotificationCenter.NotificationCenterDelegate, DialogsActivity.DialogsActivityDelegate {
|
||||
|
||||
|
@ -249,7 +260,12 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
Uri uri = intent.getData();
|
||||
if (uri != null) {
|
||||
String url = uri.toString().toLowerCase();
|
||||
isProxy = url.startsWith("tg:proxy") || url.startsWith("tg://proxy") || url.startsWith("tg:socks") || url.startsWith("tg://socks");
|
||||
isProxy = url.startsWith("tg:proxy") || url.startsWith("tg://proxy") || url.startsWith("tg:socks") || url.startsWith("tg://socks") ||
|
||||
url.startsWith(V2RayConfig.VMESS_PROTOCOL) ||
|
||||
url.startsWith(V2RayConfig.VMESS1_PROTOCOL) ||
|
||||
url.startsWith(V2RayConfig.SS_PROTOCOL) ||
|
||||
url.startsWith(V2RayConfig.SSR_PROTOCOL)
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -257,7 +273,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
long crashed_time = preferences.getLong("intro_crashed_time", 0);
|
||||
boolean fromIntro = intent.getBooleanExtra("fromIntro", false);
|
||||
if (fromIntro) {
|
||||
preferences.edit().putLong("intro_crashed_time", 0).commit();
|
||||
preferences.edit().putLong("intro_crashed_time", 0).apply();
|
||||
}
|
||||
if (!isProxy && Math.abs(crashed_time - System.currentTimeMillis()) >= 60 * 2 * 1000 && intent != null && !fromIntro) {
|
||||
preferences = ApplicationLoader.applicationContext.getSharedPreferences("logininfo2", MODE_PRIVATE);
|
||||
|
@ -313,7 +329,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
}
|
||||
});
|
||||
|
||||
if (SharedConfig.passcodeHash.length() > 0 && !SharedConfig.allowScreenCapture) {
|
||||
if (SharedConfig.passcodeHash.length() > 0 && !SharedConfig.allowScreenCapture && !NekoXConfig.disableFlagSecure) {
|
||||
try {
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
|
||||
} catch (Exception e) {
|
||||
|
@ -511,10 +527,8 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
sideMenu.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
|
||||
sideMenu.setAllowItemsInteractionDuringAnimation(false);
|
||||
sideMenu.setAdapter(drawerLayoutAdapter = new DrawerLayoutAdapter(this, itemAnimator));
|
||||
|
||||
ItemTouchHelper drawerItemTouchHelper = new ItemTouchHelper(new DrawerItemTouchHelperCallback());
|
||||
drawerItemTouchHelper.attachToRecyclerView(sideMenu);
|
||||
|
||||
drawerLayoutContainer.setDrawerLayout(sideMenu);
|
||||
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) sideMenu.getLayoutParams();
|
||||
Point screenSize = AndroidUtilities.getRealScreenSize();
|
||||
|
@ -539,6 +553,52 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
presentFragment(new LoginActivity(freeAccount));
|
||||
}
|
||||
drawerLayoutContainer.closeDrawer(false);
|
||||
} else if (view instanceof DrawerActionCheckCell) {
|
||||
int id = drawerLayoutAdapter.getId(position);
|
||||
// DrawerLayoutAdapter.CheckItem item = drawerLayoutAdapter.getItem(position);
|
||||
if (id == 12) {
|
||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("themeconfig", Activity.MODE_PRIVATE);
|
||||
String dayThemeName = preferences.getString("lastDayTheme", "Blue");
|
||||
if (Theme.getTheme(dayThemeName) == null) {
|
||||
dayThemeName = "Blue";
|
||||
}
|
||||
String nightThemeName = preferences.getString("lastDarkTheme", "Night");
|
||||
if (Theme.getTheme(nightThemeName) == null) {
|
||||
nightThemeName = "Night";
|
||||
}
|
||||
Theme.ThemeInfo themeInfo = Theme.getActiveTheme();
|
||||
|
||||
((DrawerActionCheckCell) view).setChecked(!themeInfo.isDark());
|
||||
|
||||
if (dayThemeName.equals(nightThemeName)) {
|
||||
if (themeInfo.isDark()) {
|
||||
dayThemeName = "Blue";
|
||||
} else {
|
||||
nightThemeName = "Night";
|
||||
}
|
||||
}
|
||||
|
||||
if (dayThemeName.equals(themeInfo.getKey())) {
|
||||
themeInfo = Theme.getTheme(nightThemeName);
|
||||
} else {
|
||||
themeInfo = Theme.getTheme(dayThemeName);
|
||||
}
|
||||
if (Theme.selectedAutoNightType != Theme.AUTO_NIGHT_TYPE_NONE) {
|
||||
AlertUtil.showToast(LocaleController.getString("AutoNightModeOff", R.string.AutoNightModeOff));
|
||||
Theme.selectedAutoNightType = Theme.AUTO_NIGHT_TYPE_NONE;
|
||||
Theme.saveAutoNightThemeConfig();
|
||||
Theme.cancelAutoNightThemeCallbacks();
|
||||
}
|
||||
int[] pos = new int[2];
|
||||
Switch s = ((DrawerActionCheckCell) view).checkBox;
|
||||
s.getLocationInWindow(pos);
|
||||
pos[0] += s.getMeasuredWidth() / 2;
|
||||
pos[1] += s.getMeasuredHeight() / 2;
|
||||
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.needSetDayNightTheme, themeInfo, false, pos, -1);
|
||||
} else if (id == 13) {
|
||||
presentFragment(new ProxyListActivity());
|
||||
drawerLayoutContainer.closeDrawer(false);
|
||||
}
|
||||
} else {
|
||||
int id = drawerLayoutAdapter.getId(position);
|
||||
if (id == 2) {
|
||||
|
@ -562,7 +622,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
presentFragment(new ChannelCreateActivity(args));
|
||||
} else {
|
||||
presentFragment(new ActionIntroActivity(ActionIntroActivity.ACTION_TYPE_CHANNEL_CREATE));
|
||||
preferences.edit().putBoolean("channel_intro", true).commit();
|
||||
preferences.edit().putBoolean("channel_intro", true).apply();
|
||||
}
|
||||
drawerLayoutContainer.closeDrawer(false);
|
||||
} else if (id == 6) {
|
||||
|
@ -576,7 +636,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
presentFragment(fragment);
|
||||
drawerLayoutContainer.closeDrawer(false);
|
||||
} else if (id == 9) {
|
||||
Browser.openUrl(LaunchActivity.this, LocaleController.getString("TelegramFaqUrl", R.string.TelegramFaqUrl));
|
||||
Browser.openUrl(LaunchActivity.this, NekoXConfig.FAQ_URL);
|
||||
drawerLayoutContainer.closeDrawer(false);
|
||||
} else if (id == 10) {
|
||||
presentFragment(new CallLogActivity());
|
||||
|
@ -618,6 +678,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.didSetNewWallpapper);
|
||||
NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.notificationsCountUpdated);
|
||||
NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.screenStateChanged);
|
||||
NotificationCenter.getGlobalInstance().addObserver(drawerLayoutAdapter, NotificationCenter.proxySettingsChanged);
|
||||
|
||||
if (actionBarLayout.fragmentsStack.isEmpty()) {
|
||||
if (!UserConfig.getInstance(currentAccount).isClientActivated()) {
|
||||
|
@ -745,7 +806,26 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
FileLog.e(e);
|
||||
}
|
||||
MediaController.getInstance().setBaseActivity(this, true);
|
||||
AndroidUtilities.startAppCenter(this);
|
||||
UpdateUtil.checkUpdate(this);
|
||||
UIUtil.runOnIoDispatcher(() -> {
|
||||
|
||||
for (SubInfo subInfo : SubManager.getSubList().find()) {
|
||||
|
||||
if (subInfo == null) continue;
|
||||
|
||||
try {
|
||||
|
||||
subInfo.proxies = subInfo.reloadProxies();
|
||||
subInfo.lastFetch = System.currentTimeMillis();
|
||||
|
||||
SubManager.getSubList().update(subInfo, true);
|
||||
|
||||
} catch (SubInfo.AllTriesFailed allTriesFailed) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private void checkSystemBarColors() {
|
||||
|
@ -765,6 +845,10 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
}
|
||||
|
||||
public void switchToAccount(int account, boolean removeAll) {
|
||||
switchToAccount(account, removeAll, false);
|
||||
}
|
||||
|
||||
public void switchToAccount(int account, boolean removeAll, boolean afterLogin) {
|
||||
if (account == UserConfig.selectedAccount) {
|
||||
return;
|
||||
}
|
||||
|
@ -806,6 +890,9 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
if (UserConfig.getInstance(account).unacceptedTermsOfService != null) {
|
||||
showTosActivity(account, UserConfig.getInstance(account).unacceptedTermsOfService);
|
||||
}
|
||||
if (afterLogin) {
|
||||
PrivacyUtil.postCheckAll(this, account);
|
||||
}
|
||||
updateCurrentConnectionState(currentAccount);
|
||||
}
|
||||
|
||||
|
@ -1117,15 +1204,14 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
if (!TextUtils.isEmpty(text)) {
|
||||
Matcher m = locationRegex.matcher(text);
|
||||
if (m.find()) {
|
||||
String lines[] = text.split("\\n");
|
||||
String[] lines = text.split("\\n");
|
||||
String venueTitle = null;
|
||||
String venueAddress = null;
|
||||
if (lines[0].equals("My Position")){
|
||||
if (lines[0].equals("My Position")) {
|
||||
// Use normal GeoPoint message (user position)
|
||||
}
|
||||
else if(!lines[0].contains("geo:")){
|
||||
} else if (!lines[0].contains("geo:")) {
|
||||
venueTitle = lines[0];
|
||||
if(!lines[1].contains("geo:")){
|
||||
if (!lines[1].contains("geo:")) {
|
||||
venueAddress = lines[1];
|
||||
}
|
||||
}
|
||||
|
@ -1151,11 +1237,6 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
parcelable = Uri.parse(parcelable.toString());
|
||||
}
|
||||
Uri uri = (Uri) parcelable;
|
||||
if (uri != null) {
|
||||
if (AndroidUtilities.isInternalUri(uri)) {
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
if (!error) {
|
||||
if (uri != null && (type != null && type.startsWith("image/") || uri.toString().toLowerCase().endsWith(".jpg"))) {
|
||||
if (photoPathsArray == null) {
|
||||
|
@ -1642,6 +1723,8 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
open_settings = 3;
|
||||
} else if (url.contains("folders")) {
|
||||
open_settings = 4;
|
||||
} else if (url.contains("nekox")) {
|
||||
open_settings = 101;
|
||||
} else if (url.contains("neko")) {
|
||||
open_settings = 100;
|
||||
}
|
||||
|
@ -1651,6 +1734,17 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
} catch (Exception e) {
|
||||
FileLog.e(e);
|
||||
}
|
||||
} else if (url.startsWith("tg:user") || url.startsWith("tg://user")) {
|
||||
try {
|
||||
url = url.replace("tg:user", "tg://telegram.org").replace("tg://user", "tg://telegram.org");
|
||||
data = Uri.parse(url);
|
||||
int userId = Utilities.parseInt(data.getQueryParameter("id"));
|
||||
if (userId != 0) {
|
||||
push_user_id = userId;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e(e);
|
||||
}
|
||||
} else {
|
||||
unsupportedUrl = url.replace("tg://", "").replace("tg:", "");
|
||||
int index;
|
||||
|
@ -1849,6 +1943,12 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
fragment = new FiltersSetupActivity();
|
||||
} else if (open_settings == 100) {
|
||||
fragment = new NekoSettingsActivity();
|
||||
} else if (open_settings == 101) {
|
||||
if (NekoXConfig.developerMode) {
|
||||
fragment = new NekoXSettingActivity();
|
||||
} else {
|
||||
fragment = new NekoSettingsActivity();
|
||||
}
|
||||
} else {
|
||||
fragment = null;
|
||||
}
|
||||
|
@ -1949,7 +2049,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.didReceiveSmsCode, code);
|
||||
} else {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(LaunchActivity.this);
|
||||
builder.setTitle(LocaleController.getString("AppName", R.string.AppName));
|
||||
builder.setTitle(LocaleController.getString("NekoX", R.string.NekoX));
|
||||
builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("OtherLoginCode", R.string.OtherLoginCode, code)));
|
||||
builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null);
|
||||
showAlertDialog(builder);
|
||||
|
@ -2178,7 +2278,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
}
|
||||
} else {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(LaunchActivity.this);
|
||||
builder.setTitle(LocaleController.getString("AppName", R.string.AppName));
|
||||
builder.setTitle(LocaleController.getString("NekoX", R.string.NekoX));
|
||||
if (error.text.startsWith("FLOOD_WAIT")) {
|
||||
builder.setMessage(LocaleController.getString("FloodWait", R.string.FloodWait));
|
||||
} else {
|
||||
|
@ -2232,7 +2332,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
}
|
||||
} else {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(LaunchActivity.this);
|
||||
builder.setTitle(LocaleController.getString("AppName", R.string.AppName));
|
||||
builder.setTitle(LocaleController.getString("NekoX", R.string.NekoX));
|
||||
if (error.text.startsWith("FLOOD_WAIT")) {
|
||||
builder.setMessage(LocaleController.getString("FloodWait", R.string.FloodWait));
|
||||
} else if (error.text.equals("USERS_TOO_MUCH")) {
|
||||
|
@ -2359,7 +2459,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
}
|
||||
if (response instanceof TLRPC.TL_langPackLanguage) {
|
||||
TLRPC.TL_langPackLanguage res = (TLRPC.TL_langPackLanguage) response;
|
||||
showAlertDialog(AlertsCreator.createLanguageAlert(LaunchActivity.this, res));
|
||||
AlertsCreator.createLanguageAlert(LaunchActivity.this, res).show();
|
||||
} else if (error != null) {
|
||||
if ("LANG_CODE_NOT_SUPPORTED".equals(error.text)) {
|
||||
showAlertDialog(AlertsCreator.createSimpleAlert(LaunchActivity.this, LocaleController.getString("LanguageUnsupportedError", R.string.LanguageUnsupportedError)));
|
||||
|
@ -2539,49 +2639,6 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
}
|
||||
}
|
||||
|
||||
public void checkAppUpdate(boolean force) {
|
||||
if (!force && BuildVars.DEBUG_VERSION) {
|
||||
return;
|
||||
}
|
||||
if (!force && Math.abs(System.currentTimeMillis() - UserConfig.getInstance(0).lastUpdateCheckTime) < 24 * 60 * 60 * 1000) {
|
||||
return;
|
||||
}
|
||||
TLRPC.TL_help_getAppUpdate req = new TLRPC.TL_help_getAppUpdate();
|
||||
try {
|
||||
req.source = ApplicationLoader.applicationContext.getPackageManager().getInstallerPackageName(ApplicationLoader.applicationContext.getPackageName());
|
||||
} catch (Exception ignore) {
|
||||
|
||||
}
|
||||
if (req.source == null) {
|
||||
req.source = "";
|
||||
}
|
||||
final int accountNum = currentAccount;
|
||||
ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> {
|
||||
UserConfig.getInstance(0).lastUpdateCheckTime = System.currentTimeMillis();
|
||||
UserConfig.getInstance(0).saveConfig(false);
|
||||
if (response instanceof TLRPC.TL_help_appUpdate) {
|
||||
final TLRPC.TL_help_appUpdate res = (TLRPC.TL_help_appUpdate) response;
|
||||
AndroidUtilities.runOnUIThread(() -> {
|
||||
if (res.can_not_skip) {
|
||||
UserConfig.getInstance(0).pendingAppUpdate = res;
|
||||
UserConfig.getInstance(0).pendingAppUpdateBuildVersion = BuildVars.BUILD_VERSION;
|
||||
try {
|
||||
PackageInfo packageInfo = ApplicationLoader.applicationContext.getPackageManager().getPackageInfo(ApplicationLoader.applicationContext.getPackageName(), 0);
|
||||
UserConfig.getInstance(0).pendingAppUpdateInstallTime = Math.max(packageInfo.lastUpdateTime, packageInfo.firstInstallTime);
|
||||
} catch (Exception e) {
|
||||
FileLog.e(e);
|
||||
UserConfig.getInstance(0).pendingAppUpdateInstallTime = 0;
|
||||
}
|
||||
UserConfig.getInstance(0).saveConfig(false);
|
||||
showUpdateActivity(accountNum, res, false);
|
||||
} else {
|
||||
(new UpdateAppAlertDialog(LaunchActivity.this, res, accountNum)).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public AlertDialog showAlertDialog(AlertDialog.Builder builder) {
|
||||
try {
|
||||
if (visibleDialog != null) {
|
||||
|
@ -2605,12 +2662,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
}
|
||||
localeDialog = null;
|
||||
} else if (visibleDialog == proxyErrorDialog) {
|
||||
SharedPreferences preferences = MessagesController.getGlobalMainSettings();
|
||||
SharedPreferences.Editor editor = MessagesController.getGlobalMainSettings().edit();
|
||||
editor.putBoolean("proxy_enabled", false);
|
||||
editor.putBoolean("proxy_enabled_calls", false);
|
||||
editor.commit();
|
||||
ConnectionsManager.setProxySettings(false, "", 1080, "", "", "");
|
||||
SharedConfig.setProxyEnable(false);
|
||||
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.proxySettingsChanged);
|
||||
proxyErrorDialog = null;
|
||||
}
|
||||
|
@ -2804,6 +2856,8 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.didSetPasscode);
|
||||
NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.notificationsCountUpdated);
|
||||
NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.screenStateChanged);
|
||||
NotificationCenter.getGlobalInstance().removeObserver(drawerLayoutAdapter, NotificationCenter.proxySettingsChanged);
|
||||
|
||||
}
|
||||
|
||||
public void presentFragment(BaseFragment fragment) {
|
||||
|
@ -2928,7 +2982,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
|
||||
private void showPermissionErrorAlert(String message) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(LocaleController.getString("AppName", R.string.AppName));
|
||||
builder.setTitle(LocaleController.getString("NekoX", R.string.NekoX));
|
||||
builder.setMessage(message);
|
||||
builder.setNegativeButton(LocaleController.getString("PermissionOpenSettings", R.string.PermissionOpenSettings), (dialog, which) -> {
|
||||
try {
|
||||
|
@ -2953,13 +3007,18 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
ApplicationLoader.mainInterfacePausedStageQueueTime = 0;
|
||||
});
|
||||
onPasscodePause();
|
||||
actionBarLayout.onPause();
|
||||
if (AndroidUtilities.isTablet()) {
|
||||
rightActionBarLayout.onPause();
|
||||
layersActionBarLayout.onPause();
|
||||
}
|
||||
if (passcodeView != null) {
|
||||
passcodeView.onPause();
|
||||
try {
|
||||
if (actionBarLayout != null) {
|
||||
actionBarLayout.onPause();
|
||||
}
|
||||
if (AndroidUtilities.isTablet()) {
|
||||
rightActionBarLayout.onPause();
|
||||
layersActionBarLayout.onPause();
|
||||
}
|
||||
if (passcodeView != null) {
|
||||
passcodeView.onPause();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
ConnectionsManager.getInstance(currentAccount).setAppPaused(true, false);
|
||||
if (PhotoViewer.hasInstance() && PhotoViewer.getInstance().isVisible()) {
|
||||
|
@ -3061,6 +3120,9 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
}
|
||||
ConnectionsManager.getInstance(currentAccount).setAppPaused(false, false);
|
||||
updateCurrentConnectionState(currentAccount);
|
||||
if (NekoConfig.disableProxyWhenVpnEnabled && SharedConfig.proxyEnabled && ProxyUtil.isVPNEnabled()) {
|
||||
SharedConfig.setProxyEnable(false);
|
||||
}
|
||||
if (PhotoViewer.hasInstance() && PhotoViewer.getInstance().isVisible()) {
|
||||
PhotoViewer.getInstance().onResume();
|
||||
}
|
||||
|
@ -3076,7 +3138,6 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
} else if (UserConfig.getInstance(0).pendingAppUpdate != null) {
|
||||
showUpdateActivity(UserConfig.selectedAccount, UserConfig.getInstance(0).pendingAppUpdate, true);
|
||||
}
|
||||
checkAppUpdate(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -3141,7 +3202,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
return;
|
||||
}
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(LocaleController.getString("AppName", R.string.AppName));
|
||||
builder.setTitle(LocaleController.getString("NekoX", R.string.NekoX));
|
||||
if (reason != 2 && reason != 3) {
|
||||
builder.setNegativeButton(LocaleController.getString("MoreInfo", R.string.MoreInfo), (dialogInterface, i) -> {
|
||||
if (!mainFragmentsStack.isEmpty()) {
|
||||
|
@ -3180,7 +3241,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
} else if (id == NotificationCenter.wasUnableToFindCurrentLocation) {
|
||||
final HashMap<String, MessageObject> waitingForLocation = (HashMap<String, MessageObject>) args[0];
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(LocaleController.getString("AppName", R.string.AppName));
|
||||
builder.setTitle(LocaleController.getString("NekoX", R.string.NekoX));
|
||||
builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null);
|
||||
builder.setNegativeButton(LocaleController.getString("ShareYouLocationUnableManually", R.string.ShareYouLocationUnableManually), (dialogInterface, i) -> {
|
||||
if (mainFragmentsStack.isEmpty()) {
|
||||
|
@ -3211,7 +3272,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
}
|
||||
}
|
||||
} else if (id == NotificationCenter.didSetPasscode) {
|
||||
if (SharedConfig.passcodeHash.length() > 0 && !SharedConfig.allowScreenCapture) {
|
||||
if (SharedConfig.passcodeHash.length() > 0 && !SharedConfig.allowScreenCapture && !NekoXConfig.disableFlagSecure) {
|
||||
try {
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
|
||||
} catch (Exception e) {
|
||||
|
@ -3290,7 +3351,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
themeSwitchImageView.setVisibility(View.VISIBLE);
|
||||
float finalRadius = (float) Math.max(Math.sqrt((w - pos[0]) * (w - pos[0]) + (h - pos[1]) * (h - pos[1])), Math.sqrt(pos[0] * pos[0] + (h - pos[1]) * (h - pos[1])));
|
||||
Animator anim = ViewAnimationUtils.createCircularReveal(drawerLayoutContainer, pos[0], pos[1], 0, finalRadius);
|
||||
anim.setDuration(400);
|
||||
anim.setDuration(100);
|
||||
anim.setInterpolator(Easings.easeInOutQuad);
|
||||
anim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
|
@ -3465,7 +3526,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
freeSpace = statFs.getAvailableBlocksLong() * statFs.getBlockSizeLong();
|
||||
}
|
||||
if (freeSpace < 1024 * 1024 * 100) {
|
||||
preferences.edit().putLong("last_space_check", System.currentTimeMillis()).commit();
|
||||
preferences.edit().putLong("last_space_check", System.currentTimeMillis()).apply();
|
||||
AndroidUtilities.runOnUIThread(() -> {
|
||||
try {
|
||||
AlertsCreator.createFreeSpaceDialog(LaunchActivity.this).show();
|
||||
|
@ -3532,7 +3593,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
});
|
||||
localeDialog = showAlertDialog(builder);
|
||||
SharedPreferences preferences = MessagesController.getGlobalMainSettings();
|
||||
preferences.edit().putString("language_showed2", systemLang).commit();
|
||||
preferences.edit().putString("language_showed2", systemLang).apply();
|
||||
} catch (Exception e) {
|
||||
FileLog.e(e);
|
||||
}
|
||||
|
@ -3799,33 +3860,36 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (passcodeView.getVisibility() == View.VISIBLE) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
if (SecretMediaViewer.hasInstance() && SecretMediaViewer.getInstance().isVisible()) {
|
||||
SecretMediaViewer.getInstance().closePhoto(true, false);
|
||||
} else if (PhotoViewer.hasInstance() && PhotoViewer.getInstance().isVisible()) {
|
||||
PhotoViewer.getInstance().closePhoto(true, false);
|
||||
} else if (ArticleViewer.hasInstance() && ArticleViewer.getInstance().isVisible()) {
|
||||
ArticleViewer.getInstance().close(true, false);
|
||||
} else if (drawerLayoutContainer.isDrawerOpened()) {
|
||||
drawerLayoutContainer.closeDrawer(false);
|
||||
} else if (AndroidUtilities.isTablet()) {
|
||||
if (layersActionBarLayout.getVisibility() == View.VISIBLE) {
|
||||
layersActionBarLayout.onBackPressed();
|
||||
} else {
|
||||
boolean cancel = false;
|
||||
if (rightActionBarLayout.getVisibility() == View.VISIBLE && !rightActionBarLayout.fragmentsStack.isEmpty()) {
|
||||
BaseFragment lastFragment = rightActionBarLayout.fragmentsStack.get(rightActionBarLayout.fragmentsStack.size() - 1);
|
||||
cancel = !lastFragment.onBackPressed();
|
||||
}
|
||||
if (!cancel) {
|
||||
actionBarLayout.onBackPressed();
|
||||
}
|
||||
try {
|
||||
if (passcodeView != null && passcodeView.getVisibility() == View.VISIBLE) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
actionBarLayout.onBackPressed();
|
||||
if (SecretMediaViewer.hasInstance() && SecretMediaViewer.getInstance().isVisible()) {
|
||||
SecretMediaViewer.getInstance().closePhoto(true, false);
|
||||
} else if (PhotoViewer.hasInstance() && PhotoViewer.getInstance().isVisible()) {
|
||||
PhotoViewer.getInstance().closePhoto(true, false);
|
||||
} else if (ArticleViewer.hasInstance() && ArticleViewer.getInstance().isVisible()) {
|
||||
ArticleViewer.getInstance().close(true, false);
|
||||
} else if (drawerLayoutContainer.isDrawerOpened()) {
|
||||
drawerLayoutContainer.closeDrawer(false);
|
||||
} else if (AndroidUtilities.isTablet()) {
|
||||
if (layersActionBarLayout != null && layersActionBarLayout.getVisibility() == View.VISIBLE) {
|
||||
layersActionBarLayout.onBackPressed();
|
||||
} else if (rightActionBarLayout != null) {
|
||||
boolean cancel = false;
|
||||
if (rightActionBarLayout.getVisibility() == View.VISIBLE && !rightActionBarLayout.fragmentsStack.isEmpty()) {
|
||||
BaseFragment lastFragment = rightActionBarLayout.fragmentsStack.get(rightActionBarLayout.fragmentsStack.size() - 1);
|
||||
cancel = !lastFragment.onBackPressed();
|
||||
}
|
||||
if (!cancel) {
|
||||
actionBarLayout.onBackPressed();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
actionBarLayout.onBackPressed();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3961,7 +4025,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
|||
if (ArticleViewer.hasInstance() && ArticleViewer.getInstance().isVisible()) {
|
||||
ArticleViewer.getInstance().close(false, true);
|
||||
}
|
||||
if (AndroidUtilities.isTablet()) {
|
||||
if (AndroidUtilities.isTablet() && layersActionBarLayout != null) {
|
||||
drawerLayoutContainer.setAllowOpenDrawer(!(fragment instanceof LoginActivity || fragment instanceof CountrySelectActivity) && layersActionBarLayout.getVisibility() != View.VISIBLE, true);
|
||||
if (fragment instanceof DialogsActivity) {
|
||||
DialogsActivity dialogsActivity = (DialogsActivity) fragment;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -12,7 +12,6 @@ import android.animation.ValueAnimator;
|
|||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.PorterDuff;
|
||||
|
@ -41,8 +40,6 @@ import android.widget.LinearLayout;
|
|||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
|
||||
import org.telegram.messenger.AndroidUtilities;
|
||||
import org.telegram.messenger.LocaleController;
|
||||
import org.telegram.messenger.MessagesController;
|
||||
|
@ -56,8 +53,6 @@ import org.telegram.ui.ActionBar.ActionBarMenuItem;
|
|||
import org.telegram.ui.ActionBar.BaseFragment;
|
||||
import org.telegram.ui.ActionBar.Theme;
|
||||
import org.telegram.ui.ActionBar.ThemeDescription;
|
||||
import org.telegram.ui.Cells.HeaderCell;
|
||||
import org.telegram.ui.Cells.RadioCell;
|
||||
import org.telegram.ui.Cells.ShadowSectionCell;
|
||||
import org.telegram.ui.Cells.TextInfoPrivacyCell;
|
||||
import org.telegram.ui.Cells.TextSettingsCell;
|
||||
|
@ -67,7 +62,6 @@ import org.telegram.ui.Components.LayoutHelper;
|
|||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class ProxySettingsActivity extends BaseFragment {
|
||||
|
@ -80,18 +74,17 @@ public class ProxySettingsActivity extends BaseFragment {
|
|||
private final static int FIELD_USER = 2;
|
||||
private final static int FIELD_PASSWORD = 3;
|
||||
private final static int FIELD_SECRET = 4;
|
||||
private final static int FIELD_REMARKS = 5;
|
||||
|
||||
private EditTextBoldCursor[] inputFields;
|
||||
private ScrollView scrollView;
|
||||
private LinearLayout linearLayout2;
|
||||
private LinearLayout inputFieldsContainer;
|
||||
private HeaderCell headerCell;
|
||||
private ShadowSectionCell[] sectionCell = new ShadowSectionCell[3];
|
||||
private TextInfoPrivacyCell[] bottomCells = new TextInfoPrivacyCell[2];
|
||||
private TextSettingsCell shareCell;
|
||||
private TextSettingsCell pasteCell;
|
||||
private ActionBarMenuItem doneItem;
|
||||
private RadioCell[] typeCell = new RadioCell[2];
|
||||
// private RadioCell[] typeCell = new RadioCell[2];
|
||||
private int currentType = -1;
|
||||
|
||||
private int pasteType = -1;
|
||||
|
@ -135,7 +128,7 @@ public class ProxySettingsActivity extends BaseFragment {
|
|||
addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 23 + 48 : 21, 0, LocaleController.isRTL ? 21 : 23, 0));
|
||||
|
||||
checkImage = new ImageView(context);
|
||||
checkImage.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_featuredStickers_addedIcon), PorterDuff.Mode.MULTIPLY));
|
||||
checkImage.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_featuredStickers_addedIcon), PorterDuff.Mode.SRC_IN));
|
||||
checkImage.setImageResource(R.drawable.sticker_added);
|
||||
addView(checkImage, LayoutHelper.createFrame(19, 14, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.CENTER_VERTICAL, 21, 0, 21, 0));
|
||||
}
|
||||
|
@ -163,10 +156,11 @@ public class ProxySettingsActivity extends BaseFragment {
|
|||
}
|
||||
}
|
||||
|
||||
public ProxySettingsActivity() {
|
||||
public ProxySettingsActivity(int type) {
|
||||
super();
|
||||
currentProxyInfo = new SharedConfig.ProxyInfo("", 1080, "", "", "");
|
||||
addingNewProxy = true;
|
||||
currentType = type;
|
||||
}
|
||||
|
||||
public ProxySettingsActivity(SharedConfig.ProxyInfo proxyInfo) {
|
||||
|
@ -210,6 +204,7 @@ public class ProxySettingsActivity extends BaseFragment {
|
|||
}
|
||||
currentProxyInfo.address = inputFields[FIELD_IP].getText().toString();
|
||||
currentProxyInfo.port = Utilities.parseInt(inputFields[FIELD_PORT].getText().toString());
|
||||
currentProxyInfo.setRemarks(inputFields[FIELD_REMARKS].getText().toString());
|
||||
if (currentType == 0) {
|
||||
currentProxyInfo.secret = "";
|
||||
currentProxyInfo.username = inputFields[FIELD_USER].getText().toString();
|
||||
|
@ -222,14 +217,11 @@ public class ProxySettingsActivity extends BaseFragment {
|
|||
|
||||
SharedPreferences preferences = MessagesController.getGlobalMainSettings();
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
boolean enabled;
|
||||
if (addingNewProxy) {
|
||||
SharedConfig.addProxy(currentProxyInfo);
|
||||
SharedConfig.currentProxy = currentProxyInfo;
|
||||
editor.putBoolean("proxy_enabled", true);
|
||||
enabled = true;
|
||||
SharedConfig.setCurrentProxy(currentProxyInfo);
|
||||
} else {
|
||||
enabled = preferences.getBoolean("proxy_enabled", false);
|
||||
SharedConfig.setProxyEnable(false);
|
||||
SharedConfig.saveProxyList();
|
||||
}
|
||||
if (addingNewProxy || SharedConfig.currentProxy == currentProxyInfo) {
|
||||
|
@ -238,13 +230,21 @@ public class ProxySettingsActivity extends BaseFragment {
|
|||
editor.putString("proxy_user", currentProxyInfo.username);
|
||||
editor.putInt("proxy_port", currentProxyInfo.port);
|
||||
editor.putString("proxy_secret", currentProxyInfo.secret);
|
||||
ConnectionsManager.setProxySettings(enabled, currentProxyInfo.address, currentProxyInfo.port, currentProxyInfo.username, currentProxyInfo.password, currentProxyInfo.secret);
|
||||
if (currentProxyInfo instanceof SharedConfig.VmessProxy) {
|
||||
editor.putString("vmess_link", ((SharedConfig.VmessProxy) currentProxyInfo).bean.toString());
|
||||
} else if (currentProxyInfo instanceof SharedConfig.ShadowsocksProxy) {
|
||||
editor.putString("vmess_link", ((SharedConfig.ShadowsocksProxy) currentProxyInfo).bean.toString());
|
||||
} else if (currentProxyInfo instanceof SharedConfig.ShadowsocksRProxy) {
|
||||
editor.putString("vmess_link", ((SharedConfig.ShadowsocksRProxy) currentProxyInfo).bean.toString());
|
||||
}
|
||||
ConnectionsManager.setProxySettings(SharedConfig.proxyEnabled, currentProxyInfo.address, currentProxyInfo.port, currentProxyInfo.username, currentProxyInfo.password, currentProxyInfo.secret);
|
||||
}
|
||||
editor.commit();
|
||||
editor.apply();
|
||||
|
||||
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.proxySettingsChanged);
|
||||
|
||||
finishFragment();
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -265,23 +265,7 @@ public class ProxySettingsActivity extends BaseFragment {
|
|||
linearLayout2.setOrientation(LinearLayout.VERTICAL);
|
||||
scrollView.addView(linearLayout2, new ScrollView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
final View.OnClickListener typeCellClickListener = view -> setProxyType((Integer) view.getTag(), true);
|
||||
|
||||
for (int a = 0; a < 2; a++) {
|
||||
typeCell[a] = new RadioCell(context);
|
||||
typeCell[a].setBackground(Theme.getSelectorDrawable(true));
|
||||
typeCell[a].setTag(a);
|
||||
if (a == 0) {
|
||||
typeCell[a].setText(LocaleController.getString("UseProxySocks5", R.string.UseProxySocks5), a == currentType, true);
|
||||
} else if (a == 1) {
|
||||
typeCell[a].setText(LocaleController.getString("UseProxyTelegram", R.string.UseProxyTelegram), a == currentType, false);
|
||||
}
|
||||
linearLayout2.addView(typeCell[a], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 50));
|
||||
typeCell[a].setOnClickListener(typeCellClickListener);
|
||||
}
|
||||
|
||||
sectionCell[0] = new ShadowSectionCell(context);
|
||||
linearLayout2.addView(sectionCell[0], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
|
||||
|
||||
inputFieldsContainer = new LinearLayout(context);
|
||||
inputFieldsContainer.setOrientation(LinearLayout.VERTICAL);
|
||||
|
@ -293,8 +277,8 @@ public class ProxySettingsActivity extends BaseFragment {
|
|||
}
|
||||
linearLayout2.addView(inputFieldsContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
|
||||
|
||||
inputFields = new EditTextBoldCursor[5];
|
||||
for (int a = 0; a < 5; a++) {
|
||||
inputFields = new EditTextBoldCursor[6];
|
||||
for (int a = 0; a < 6; a++) {
|
||||
FrameLayout container = new FrameLayout(context);
|
||||
inputFieldsContainer.addView(container, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
|
||||
|
@ -409,6 +393,10 @@ public class ProxySettingsActivity extends BaseFragment {
|
|||
inputFields[a].setHintText(LocaleController.getString("UseProxySecret", R.string.UseProxySecret));
|
||||
inputFields[a].setText(currentProxyInfo.secret);
|
||||
break;
|
||||
case FIELD_REMARKS:
|
||||
inputFields[a].setHintText(LocaleController.getString("ProxyRemarks", R.string.ProxyRemarks));
|
||||
inputFields[a].setText(currentProxyInfo.getRemarks());
|
||||
break;
|
||||
}
|
||||
inputFields[a].setSelection(inputFields[a].length());
|
||||
|
||||
|
@ -488,64 +476,6 @@ public class ProxySettingsActivity extends BaseFragment {
|
|||
linearLayout2.addView(sectionCell[2], 1, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
|
||||
sectionCell[2].setVisibility(View.GONE);
|
||||
|
||||
shareCell = new TextSettingsCell(context);
|
||||
shareCell.setBackgroundDrawable(Theme.getSelectorDrawable(true));
|
||||
shareCell.setText(LocaleController.getString("ShareFile", R.string.ShareFile), false);
|
||||
shareCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4));
|
||||
linearLayout2.addView(shareCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
|
||||
shareCell.setOnClickListener(v -> {
|
||||
StringBuilder params = new StringBuilder();
|
||||
String address = inputFields[FIELD_IP].getText().toString();
|
||||
String password = inputFields[FIELD_PASSWORD].getText().toString();
|
||||
String user = inputFields[FIELD_USER].getText().toString();
|
||||
String port = inputFields[FIELD_PORT].getText().toString();
|
||||
String secret = inputFields[FIELD_SECRET].getText().toString();
|
||||
String url;
|
||||
try {
|
||||
if (!TextUtils.isEmpty(address)) {
|
||||
params.append("server=").append(URLEncoder.encode(address, "UTF-8"));
|
||||
}
|
||||
if (!TextUtils.isEmpty(port)) {
|
||||
if (params.length() != 0) {
|
||||
params.append("&");
|
||||
}
|
||||
params.append("port=").append(URLEncoder.encode(port, "UTF-8"));
|
||||
}
|
||||
if (currentType == 1) {
|
||||
url = "https://t.me/proxy?";
|
||||
if (params.length() != 0) {
|
||||
params.append("&");
|
||||
}
|
||||
params.append("secret=").append(URLEncoder.encode(secret, "UTF-8"));
|
||||
} else {
|
||||
url = "https://t.me/socks?";
|
||||
if (!TextUtils.isEmpty(user)) {
|
||||
if (params.length() != 0) {
|
||||
params.append("&");
|
||||
}
|
||||
params.append("user=").append(URLEncoder.encode(user, "UTF-8"));
|
||||
}
|
||||
if (!TextUtils.isEmpty(password)) {
|
||||
if (params.length() != 0) {
|
||||
params.append("&");
|
||||
}
|
||||
params.append("pass=").append(URLEncoder.encode(password, "UTF-8"));
|
||||
}
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
return;
|
||||
}
|
||||
if (params.length() == 0) {
|
||||
return;
|
||||
}
|
||||
Intent shareIntent = new Intent(Intent.ACTION_SEND);
|
||||
shareIntent.setType("text/plain");
|
||||
shareIntent.putExtra(Intent.EXTRA_TEXT, url + params.toString());
|
||||
Intent chooserIntent = Intent.createChooser(shareIntent, LocaleController.getString("ShareLink", R.string.ShareLink));
|
||||
chooserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
getParentActivity().startActivity(chooserIntent);
|
||||
});
|
||||
|
||||
sectionCell[1] = new ShadowSectionCell(context);
|
||||
sectionCell[1].setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow));
|
||||
linearLayout2.addView(sectionCell[1], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
|
||||
|
@ -556,8 +486,19 @@ public class ProxySettingsActivity extends BaseFragment {
|
|||
shareDoneProgress = 1f;
|
||||
checkShareDone(false);
|
||||
|
||||
currentType = -1;
|
||||
setProxyType(TextUtils.isEmpty(currentProxyInfo.secret) ? 0 : 1, false);
|
||||
if (currentType == -1) {
|
||||
|
||||
setProxyType(TextUtils.isEmpty(currentProxyInfo.secret) ? 0 : 1, false);
|
||||
|
||||
} else {
|
||||
|
||||
int t = currentType;
|
||||
|
||||
currentType = -1;
|
||||
|
||||
setProxyType(t, false);
|
||||
|
||||
}
|
||||
|
||||
pasteType = -1;
|
||||
pasteString = null;
|
||||
|
@ -665,7 +606,6 @@ public class ProxySettingsActivity extends BaseFragment {
|
|||
shareDoneAnimator.setDuration(200);
|
||||
shareDoneAnimator.addUpdateListener(a -> {
|
||||
shareDoneProgress = AndroidUtilities.lerp(shareDoneProgressAnimValues, a.getAnimatedFraction());
|
||||
shareCell.setTextColor(ColorUtils.blendARGB(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2), Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4), shareDoneProgress));
|
||||
doneItem.setAlpha(shareDoneProgress / 2f + 0.5f);
|
||||
});
|
||||
}
|
||||
|
@ -675,17 +615,15 @@ public class ProxySettingsActivity extends BaseFragment {
|
|||
shareDoneAnimator.start();
|
||||
} else {
|
||||
shareDoneProgress = enabled ? 1f : 0f;
|
||||
shareCell.setTextColor(enabled ? Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4) : Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2));
|
||||
doneItem.setAlpha(enabled ? 1f : .5f);
|
||||
}
|
||||
shareCell.setEnabled(enabled);
|
||||
doneItem.setEnabled(enabled);
|
||||
shareDoneEnabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
private void checkShareDone(boolean animated) {
|
||||
if (shareCell == null || doneItem == null || inputFields[FIELD_IP] == null || inputFields[FIELD_PORT] == null) {
|
||||
if (doneItem == null || inputFields[FIELD_IP] == null || inputFields[FIELD_PORT] == null) {
|
||||
return;
|
||||
}
|
||||
setShareDoneEnabled(inputFields[FIELD_IP].length() != 0 && Utilities.parseInt(inputFields[FIELD_PORT].getText().toString()) != 0, animated);
|
||||
|
@ -749,8 +687,6 @@ public class ProxySettingsActivity extends BaseFragment {
|
|||
((View) inputFields[FIELD_PASSWORD].getParent()).setVisibility(View.GONE);
|
||||
((View) inputFields[FIELD_USER].getParent()).setVisibility(View.GONE);
|
||||
}
|
||||
typeCell[0].setChecked(currentType == 0, animated);
|
||||
typeCell[1].setChecked(currentType == 1, animated);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -765,9 +701,6 @@ public class ProxySettingsActivity extends BaseFragment {
|
|||
@Override
|
||||
public ArrayList<ThemeDescription> getThemeDescriptions() {
|
||||
final ThemeDescription.ThemeDescriptionDelegate delegate = () -> {
|
||||
if (shareCell != null && (shareDoneAnimator == null || !shareDoneAnimator.isRunning())) {
|
||||
shareCell.setTextColor(shareDoneEnabled ? Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4) : Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2));
|
||||
}
|
||||
if (inputFields != null) {
|
||||
for (int i = 0; i < inputFields.length; i++) {
|
||||
inputFields[i].setLineColors(Theme.getColor(Theme.key_windowBackgroundWhiteInputField),
|
||||
|
@ -788,23 +721,10 @@ public class ProxySettingsActivity extends BaseFragment {
|
|||
arrayList.add(new ThemeDescription(inputFieldsContainer, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite));
|
||||
arrayList.add(new ThemeDescription(linearLayout2, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider));
|
||||
|
||||
arrayList.add(new ThemeDescription(shareCell, ThemeDescription.FLAG_SELECTORWHITE, null, null, null, null, Theme.key_windowBackgroundWhite));
|
||||
arrayList.add(new ThemeDescription(shareCell, ThemeDescription.FLAG_SELECTORWHITE, null, null, null, null, Theme.key_listSelector));
|
||||
arrayList.add(new ThemeDescription(null, 0, null, null, null, null, delegate, Theme.key_windowBackgroundWhiteBlueText4));
|
||||
arrayList.add(new ThemeDescription(null, 0, null, null, null, null, delegate, Theme.key_windowBackgroundWhiteGrayText2));
|
||||
|
||||
arrayList.add(new ThemeDescription(pasteCell, ThemeDescription.FLAG_SELECTORWHITE, null, null, null, null, Theme.key_windowBackgroundWhite));
|
||||
arrayList.add(new ThemeDescription(pasteCell, ThemeDescription.FLAG_SELECTORWHITE, null, null, null, null, Theme.key_listSelector));
|
||||
arrayList.add(new ThemeDescription(pasteCell, 0, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueText4));
|
||||
|
||||
for (int a = 0; a < typeCell.length; a++) {
|
||||
arrayList.add(new ThemeDescription(typeCell[a], ThemeDescription.FLAG_SELECTORWHITE, null, null, null, null, Theme.key_windowBackgroundWhite));
|
||||
arrayList.add(new ThemeDescription(typeCell[a], ThemeDescription.FLAG_SELECTORWHITE, null, null, null, null, Theme.key_listSelector));
|
||||
arrayList.add(new ThemeDescription(typeCell[a], 0, new Class[]{RadioCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
|
||||
arrayList.add(new ThemeDescription(typeCell[a], ThemeDescription.FLAG_CHECKBOX, new Class[]{RadioCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackground));
|
||||
arrayList.add(new ThemeDescription(typeCell[a], ThemeDescription.FLAG_CHECKBOXCHECK, new Class[]{RadioCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackgroundChecked));
|
||||
}
|
||||
|
||||
if (inputFields != null) {
|
||||
for (int a = 0; a < inputFields.length; a++) {
|
||||
arrayList.add(new ThemeDescription(inputFields[a], ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
|
||||
|
@ -819,8 +739,6 @@ public class ProxySettingsActivity extends BaseFragment {
|
|||
arrayList.add(new ThemeDescription(null, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
|
||||
arrayList.add(new ThemeDescription(null, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText));
|
||||
}
|
||||
arrayList.add(new ThemeDescription(headerCell, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite));
|
||||
arrayList.add(new ThemeDescription(headerCell, 0, new Class[]{HeaderCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader));
|
||||
for (int a = 0; a < sectionCell.length; a++) {
|
||||
if (sectionCell[a] != null) {
|
||||
arrayList.add(new ThemeDescription(sectionCell[a], ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ShadowSectionCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow));
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
/*******************************************************************************
|
||||
* *
|
||||
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
|
||||
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
|
||||
* *
|
||||
* 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 <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
package tw.nekomimi.nekogram
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import android.os.SystemClock
|
||||
import android.system.ErrnoException
|
||||
import android.system.Os
|
||||
import android.system.OsConstants
|
||||
import androidx.annotation.MainThread
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import org.telegram.messenger.ApplicationLoader
|
||||
import org.telegram.messenger.FileLog
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
class GuardedProcessPool(private val onFatal: suspend (IOException) -> Unit) : CoroutineScope {
|
||||
companion object {
|
||||
private const val TAG = "GuardedProcessPool"
|
||||
}
|
||||
|
||||
private inner class Guard(private val cmd: List<String>) {
|
||||
private lateinit var process: Process
|
||||
|
||||
private fun streamLogger(input: InputStream, logger: (String) -> Unit) = try {
|
||||
input.bufferedReader().forEachLine(logger)
|
||||
} catch (_: IOException) { } // ignore
|
||||
|
||||
fun start() {
|
||||
process = ProcessBuilder(cmd).directory(ApplicationLoader.applicationContext.cacheDir).start()
|
||||
}
|
||||
|
||||
suspend fun looper(onRestartCallback: (suspend () -> Unit)?) {
|
||||
var running = true
|
||||
val cmdName = File(cmd.first()).nameWithoutExtension
|
||||
val exitChannel = Channel<Int>()
|
||||
try {
|
||||
while (true) {
|
||||
thread(name = "stderr-$cmdName") {
|
||||
streamLogger(process.errorStream) {
|
||||
FileLog.e("[$cmdName]$it")
|
||||
}
|
||||
}
|
||||
thread(name = "stdout-$cmdName") {
|
||||
streamLogger(process.inputStream) {
|
||||
FileLog.d("[$cmdName]$it")
|
||||
}
|
||||
// this thread also acts as a daemon thread for waitFor
|
||||
runBlocking { exitChannel.send(process.waitFor()) }
|
||||
}
|
||||
val startTime = SystemClock.elapsedRealtime()
|
||||
val exitCode = exitChannel.receive()
|
||||
running = false
|
||||
when {
|
||||
SystemClock.elapsedRealtime() - startTime < 1000 -> throw IOException(
|
||||
"$cmdName exits too fast (exit code: $exitCode)")
|
||||
exitCode == 128 + OsConstants.SIGKILL -> FileLog.w("$cmdName was killed")
|
||||
else -> FileLog.e(IOException("$cmdName unexpectedly exits with code $exitCode"))
|
||||
}
|
||||
start()
|
||||
running = true
|
||||
onRestartCallback?.invoke()
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
FileLog.w("error occurred. stop guard: " + cmd.joinToString(" "))
|
||||
GlobalScope.launch(Dispatchers.Main) { onFatal(e) }
|
||||
} finally {
|
||||
if (running) withContext(NonCancellable) {
|
||||
process.destroy() // kill the process
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
if (withTimeoutOrNull(1000) { exitChannel.receive() } != null) return@withContext
|
||||
process.destroyForcibly() // Force to kill the process if it's still alive
|
||||
}
|
||||
exitChannel.receive()
|
||||
} // otherwise process already exited, nothing to be done
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val coroutineContext = Dispatchers.Main.immediate + Job()
|
||||
|
||||
@MainThread
|
||||
fun start(cmd: List<String>, onRestartCallback: (suspend () -> Unit)? = null) {
|
||||
FileLog.d("start process: " + cmd.joinToString (" "))
|
||||
Guard(cmd).apply {
|
||||
start() // if start fails, IOException will be thrown directly
|
||||
launch { looper(onRestartCallback) }
|
||||
}
|
||||
}
|
||||
|
||||
@MainThread
|
||||
fun close(scope: CoroutineScope) {
|
||||
cancel()
|
||||
coroutineContext[Job]!!.also { job -> scope.launch { job.join() } }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package tw.nekomimi.nekogram
|
||||
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.ServerSocket
|
||||
import kotlin.random.Random
|
||||
|
||||
object ProxyManager {
|
||||
|
||||
@JvmStatic
|
||||
fun mkPort(): Int {
|
||||
|
||||
var port: Int
|
||||
|
||||
do {
|
||||
|
||||
port = mkNewPort()
|
||||
|
||||
} while (!isProxyAvailable(port))
|
||||
|
||||
return port
|
||||
|
||||
}
|
||||
|
||||
private fun mkNewPort() = Random.nextInt(2048, 32768)
|
||||
|
||||
@JvmStatic
|
||||
fun isProxyAvailable(port: Int): Boolean {
|
||||
|
||||
if (port !in 2048 until 32768) return false
|
||||
|
||||
runCatching {
|
||||
|
||||
val server = ServerSocket()
|
||||
|
||||
server.bind(InetSocketAddress("127.0.0.1",port))
|
||||
|
||||
server.close()
|
||||
|
||||
Thread.sleep(1000L)
|
||||
|
||||
}.onFailure {
|
||||
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
package tw.nekomimi.nekogram
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import com.v2ray.ang.V2RayConfig
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import org.telegram.messenger.ApplicationLoader
|
||||
import org.telegram.messenger.FileLog
|
||||
import tw.nekomimi.nekogram.utils.FileUtil
|
||||
import java.io.File
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
class RelayBatonLoader {
|
||||
|
||||
companion object {
|
||||
|
||||
@SuppressLint("AuthLeak")
|
||||
const val publicServer = "rb://anonymous:nya@net.neko.services"
|
||||
|
||||
}
|
||||
|
||||
lateinit var bean: Bean
|
||||
var port by Delegates.notNull<Int>()
|
||||
var rbProcess: GuardedProcessPool? = null
|
||||
|
||||
fun initConfig(bean: Bean, port: Int) {
|
||||
|
||||
this.bean = bean
|
||||
this.port = port
|
||||
|
||||
}
|
||||
|
||||
fun start() {
|
||||
|
||||
stop()
|
||||
|
||||
val cacheCfg = File(ApplicationLoader.applicationContext.cacheDir, "config.toml")
|
||||
|
||||
cacheCfg.writeText(bean.toToml(port))
|
||||
|
||||
val geoip = File(ApplicationLoader.applicationContext.cacheDir, "GeoLite2-Country.mmdb")
|
||||
|
||||
if (!geoip.isFile) {
|
||||
|
||||
geoip.createNewFile()
|
||||
|
||||
geoip.outputStream().use { out ->
|
||||
|
||||
ApplicationLoader.applicationContext.assets.open("GeoLite2-Country.mmdb").use {
|
||||
|
||||
it.copyTo(out)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
rbProcess = GuardedProcessPool {
|
||||
|
||||
FileLog.e(it)
|
||||
|
||||
}.apply {
|
||||
|
||||
runCatching {
|
||||
|
||||
start(listOf(FileUtil.extLib("relaybaton").path, "client")) {
|
||||
|
||||
cacheCfg.delete()
|
||||
|
||||
}
|
||||
|
||||
}.onFailure {
|
||||
|
||||
cacheCfg.delete()
|
||||
|
||||
FileLog.e(it)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
|
||||
if (rbProcess != null) {
|
||||
|
||||
val proc = rbProcess!!
|
||||
|
||||
thread {
|
||||
|
||||
runCatching {
|
||||
|
||||
runBlocking { proc.close(this) }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
rbProcess = null
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data class Bean(
|
||||
var server: String = "",
|
||||
var username: String = "",
|
||||
var password: String = "",
|
||||
var esni: Boolean = true,
|
||||
var remarks: String? = null
|
||||
) {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return super.equals(other) || (other is Bean && hash == other.hash)
|
||||
}
|
||||
|
||||
val hash = (server + username + password).hashCode()
|
||||
|
||||
fun toToml(port: Int) = """
|
||||
[log]
|
||||
file="./log.txt"
|
||||
level="error"
|
||||
|
||||
[dns]
|
||||
type="dot"
|
||||
server="cloudflare-dns.com"
|
||||
addr="1.0.0.1:853"
|
||||
local_resolve=false
|
||||
|
||||
[clients]
|
||||
port=$port
|
||||
[[clients.client]]
|
||||
id="1"
|
||||
server="$server"
|
||||
username="$username"
|
||||
password="$password"
|
||||
esni=$esni
|
||||
timeout=15
|
||||
|
||||
[routes]
|
||||
geoip_file="GeoLite2-Country.mmdb"
|
||||
[[routes.route]]
|
||||
type="default"
|
||||
cond=""
|
||||
target="1"
|
||||
"""
|
||||
|
||||
companion object {
|
||||
|
||||
fun parse(url: String): Bean {
|
||||
|
||||
// ss-android style
|
||||
|
||||
val link = url.replace(V2RayConfig.RB_PROTOCOL, "https://").toHttpUrlOrNull() ?: error("invalid relaybaton link $url")
|
||||
|
||||
return Bean(
|
||||
link.host,
|
||||
link.username,
|
||||
link.password,
|
||||
link.queryParameter("esni").takeIf { it in arrayOf("true", "false") }?.toBoolean() ?: true,
|
||||
link.fragment
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
|
||||
val url = HttpUrl.Builder()
|
||||
.scheme("https")
|
||||
.username(username)
|
||||
.password(password)
|
||||
.host(server)
|
||||
|
||||
if (!remarks.isNullOrBlank()) url.fragment(remarks)
|
||||
|
||||
if (!esni) url.addQueryParameter("esni","false")
|
||||
|
||||
return url.build().toString().replace("https://", V2RayConfig.RB_PROTOCOL)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,389 @@
|
|||
/*
|
||||
* This is the source code of Telegram for Android v. 5.x.x.
|
||||
* It is licensed under GNU GPL v. 2 or later.
|
||||
* You should have received a copy of the license in this archive (see LICENSE).
|
||||
*
|
||||
* Copyright Nikolai Kudashov, 2013-2018.
|
||||
*/
|
||||
|
||||
package tw.nekomimi.nekogram;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.os.Build;
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.github.shadowsocks.plugin.PluginConfiguration;
|
||||
import com.github.shadowsocks.plugin.PluginContract;
|
||||
import com.github.shadowsocks.plugin.PluginList;
|
||||
import com.github.shadowsocks.plugin.PluginManager;
|
||||
import com.github.shadowsocks.plugin.PluginOptions;
|
||||
|
||||
import org.telegram.messenger.AndroidUtilities;
|
||||
import org.telegram.messenger.LocaleController;
|
||||
import org.telegram.messenger.R;
|
||||
import org.telegram.messenger.SharedConfig;
|
||||
import org.telegram.messenger.Utilities;
|
||||
import org.telegram.ui.ActionBar.ActionBar;
|
||||
import org.telegram.ui.ActionBar.ActionBarMenuItem;
|
||||
import org.telegram.ui.ActionBar.BaseFragment;
|
||||
import org.telegram.ui.ActionBar.Theme;
|
||||
import org.telegram.ui.ActionBar.ThemeDescription;
|
||||
import org.telegram.ui.Cells.TextCheckCell;
|
||||
import org.telegram.ui.Cells.TextInfoPrivacyCell;
|
||||
import org.telegram.ui.Cells.TextSettingsCell;
|
||||
import org.telegram.ui.Components.EditTextBoldCursor;
|
||||
import org.telegram.ui.Components.LayoutHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import kotlin.Unit;
|
||||
import tw.nekomimi.nekogram.utils.AlertUtil;
|
||||
import tw.nekomimi.nekogram.utils.PopupBuilder;
|
||||
|
||||
public class RelayBatonSettingsActivity extends BaseFragment {
|
||||
|
||||
private EditTextBoldCursor[] inputFields;
|
||||
|
||||
private EditTextBoldCursor serverField;
|
||||
private EditTextBoldCursor usernameField;
|
||||
private EditTextBoldCursor passwordField;
|
||||
private TextCheckCell esniField;
|
||||
private EditTextBoldCursor remarksField;
|
||||
|
||||
private ScrollView scrollView;
|
||||
private LinearLayout linearLayout2;
|
||||
private LinearLayout inputFieldsContainer;
|
||||
|
||||
private TextInfoPrivacyCell bottomCell;
|
||||
|
||||
private SharedConfig.RelayBatonProxy currentProxyInfo;
|
||||
private RelayBatonLoader.Bean currentBean;
|
||||
|
||||
private boolean ignoreOnTextChange;
|
||||
|
||||
private static final int done_button = 1;
|
||||
|
||||
public class TypeCell extends FrameLayout {
|
||||
|
||||
private TextView textView;
|
||||
private ImageView checkImage;
|
||||
private boolean needDivider;
|
||||
|
||||
public TypeCell(Context context) {
|
||||
super(context);
|
||||
|
||||
setWillNotDraw(false);
|
||||
|
||||
textView = new TextView(context);
|
||||
textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));
|
||||
textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||
textView.setLines(1);
|
||||
textView.setMaxLines(1);
|
||||
textView.setSingleLine(true);
|
||||
textView.setEllipsize(TextUtils.TruncateAt.END);
|
||||
textView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL);
|
||||
addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 23 + 48 : 21, 0, LocaleController.isRTL ? 21 : 23, 0));
|
||||
|
||||
checkImage = new ImageView(context);
|
||||
checkImage.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_featuredStickers_addedIcon), PorterDuff.Mode.SRC_IN));
|
||||
checkImage.setImageResource(R.drawable.sticker_added);
|
||||
addView(checkImage, LayoutHelper.createFrame(19, 14, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.CENTER_VERTICAL, 21, 0, 21, 0));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(50) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY));
|
||||
}
|
||||
|
||||
public void setValue(String name, boolean checked, boolean divider) {
|
||||
textView.setText(name);
|
||||
checkImage.setVisibility(checked ? VISIBLE : INVISIBLE);
|
||||
needDivider = divider;
|
||||
}
|
||||
|
||||
public void setTypeChecked(boolean value) {
|
||||
checkImage.setVisibility(value ? VISIBLE : INVISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
if (needDivider) {
|
||||
canvas.drawLine(LocaleController.isRTL ? 0 : AndroidUtilities.dp(20), getMeasuredHeight() - 1, getMeasuredWidth() - (LocaleController.isRTL ? AndroidUtilities.dp(20) : 0), getMeasuredHeight() - 1, Theme.dividerPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public RelayBatonSettingsActivity() {
|
||||
super();
|
||||
currentBean = new RelayBatonLoader.Bean();
|
||||
}
|
||||
|
||||
public RelayBatonSettingsActivity(SharedConfig.RelayBatonProxy proxyInfo) {
|
||||
super();
|
||||
currentProxyInfo = proxyInfo;
|
||||
currentBean = proxyInfo.bean;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
AndroidUtilities.requestAdjustResize(getParentActivity(), classGuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View createView(Context context) {
|
||||
actionBar.setTitle(LocaleController.getString("ProxyDetails", R.string.ProxyDetails));
|
||||
actionBar.setBackButtonImage(R.drawable.ic_ab_back);
|
||||
actionBar.setAllowOverlayTitle(false);
|
||||
if (AndroidUtilities.isTablet()) {
|
||||
actionBar.setOccupyStatusBar(false);
|
||||
}
|
||||
|
||||
actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() {
|
||||
@Override
|
||||
public void onItemClick(int id) {
|
||||
if (id == -1) {
|
||||
finishFragment();
|
||||
} else if (id == done_button) {
|
||||
|
||||
if (getParentActivity() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(serverField.getText())) {
|
||||
|
||||
serverField.requestFocus();
|
||||
AndroidUtilities.showKeyboard(serverField);
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(usernameField.getText())) {
|
||||
|
||||
usernameField.requestFocus();
|
||||
AndroidUtilities.showKeyboard(usernameField);
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(passwordField.getText())) {
|
||||
|
||||
passwordField.requestFocus();
|
||||
AndroidUtilities.showKeyboard(passwordField);
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
|
||||
currentBean.setServer(serverField.getText().toString());
|
||||
currentBean.setUsername(usernameField.getText().toString());
|
||||
currentBean.setPassword(passwordField.getText().toString());
|
||||
currentBean.setEsni(esniField.isChecked());
|
||||
currentBean.setRemarks(remarksField.getText().toString());
|
||||
|
||||
if (currentProxyInfo == null) {
|
||||
currentProxyInfo = new SharedConfig.RelayBatonProxy(currentBean);
|
||||
SharedConfig.addProxy(currentProxyInfo);
|
||||
SharedConfig.setCurrentProxy(currentProxyInfo);
|
||||
} else {
|
||||
currentProxyInfo.proxyCheckPingId = 0;
|
||||
currentProxyInfo.availableCheckTime = 0;
|
||||
currentProxyInfo.ping = 0;
|
||||
SharedConfig.saveProxyList();
|
||||
SharedConfig.setProxyEnable(false);
|
||||
}
|
||||
|
||||
finishFragment();
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ActionBarMenuItem doneItem = actionBar.createMenu().addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56));
|
||||
doneItem.setContentDescription(LocaleController.getString("Done", R.string.Done));
|
||||
|
||||
fragmentView = new FrameLayout(context);
|
||||
FrameLayout frameLayout = (FrameLayout) fragmentView;
|
||||
fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray));
|
||||
|
||||
scrollView = new ScrollView(context);
|
||||
scrollView.setFillViewport(true);
|
||||
AndroidUtilities.setScrollViewEdgeEffectColor(scrollView, Theme.getColor(Theme.key_actionBarDefault));
|
||||
frameLayout.addView(scrollView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT));
|
||||
|
||||
linearLayout2 = new LinearLayout(context);
|
||||
linearLayout2.setOrientation(LinearLayout.VERTICAL);
|
||||
scrollView.addView(linearLayout2, new ScrollView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
inputFieldsContainer = new LinearLayout(context);
|
||||
inputFieldsContainer.setOrientation(LinearLayout.VERTICAL);
|
||||
inputFieldsContainer.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite));
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
// bring to front for transitions
|
||||
inputFieldsContainer.setElevation(AndroidUtilities.dp(1f));
|
||||
inputFieldsContainer.setOutlineProvider(null);
|
||||
}
|
||||
linearLayout2.addView(inputFieldsContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
|
||||
|
||||
inputFields = new EditTextBoldCursor[4];
|
||||
|
||||
for (int a = 0; a < 4; a++) {
|
||||
FrameLayout container = new FrameLayout(context);
|
||||
EditTextBoldCursor cursor = mkCursor();
|
||||
inputFields[a] = cursor;
|
||||
cursor.setImeOptions(EditorInfo.IME_ACTION_NEXT | EditorInfo.IME_FLAG_NO_EXTRACT_UI);
|
||||
switch (a) {
|
||||
case 0:
|
||||
serverField = cursor;
|
||||
cursor.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
cursor.setHintText(LocaleController.getString("UseProxyAddress", R.string.UseProxyAddress));
|
||||
cursor.setText(currentBean.getServer());
|
||||
break;
|
||||
case 1:
|
||||
usernameField = cursor;
|
||||
cursor.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
cursor.setHintText(LocaleController.getString("UseProxyUsername", R.string.UseProxyUsername));
|
||||
cursor.setText(currentBean.getUsername());
|
||||
break;
|
||||
case 2:
|
||||
passwordField = cursor;
|
||||
cursor.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
cursor.setHintText(LocaleController.getString("UseProxyPassword", R.string.UseProxyPassword));
|
||||
cursor.setText(currentBean.getPassword());
|
||||
break;
|
||||
case 3:
|
||||
remarksField = cursor;
|
||||
cursor.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
cursor.setHintText(LocaleController.getString("ProxyRemarks", R.string.ProxyRemarks));
|
||||
cursor.setText(currentBean.getRemarks());
|
||||
break;
|
||||
}
|
||||
cursor.setSelection(cursor.length());
|
||||
|
||||
cursor.setPadding(0, 0, 0, 0);
|
||||
container.addView(cursor, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 17, a == 0 ? 12 : 0, 17, 0));
|
||||
|
||||
}
|
||||
|
||||
inputFieldsContainer.addView((View) serverField.getParent(), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
inputFieldsContainer.addView((View) usernameField.getParent(), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
inputFieldsContainer.addView((View) passwordField.getParent(), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
|
||||
FrameLayout container = new FrameLayout(context);
|
||||
esniField = new TextCheckCell(context);
|
||||
esniField.setBackground(Theme.getSelectorDrawable(false));
|
||||
esniField.setTextAndCheck(LocaleController.getString("ESNI", R.string.ESNI), currentBean.getEsni(), false);
|
||||
esniField.setOnClickListener((v) -> esniField.setChecked(!esniField.isChecked()));
|
||||
container.addView(esniField, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 0, 0, 0, 0));
|
||||
inputFieldsContainer.addView(container, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
|
||||
inputFieldsContainer.addView((View) remarksField.getParent(), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
|
||||
bottomCell = new TextInfoPrivacyCell(context);
|
||||
bottomCell.setBackground(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow));
|
||||
bottomCell.setText(LocaleController.getString("ProxyInfoSS", R.string.ProxyInfoSS));
|
||||
linearLayout2.addView(bottomCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
|
||||
|
||||
return fragmentView;
|
||||
|
||||
}
|
||||
|
||||
EditTextBoldCursor mkCursor() {
|
||||
|
||||
EditTextBoldCursor cursor = new EditTextBoldCursor(getParentActivity());
|
||||
cursor.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||
cursor.setHintColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText));
|
||||
cursor.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));
|
||||
cursor.setBackground(null);
|
||||
cursor.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));
|
||||
cursor.setCursorSize(AndroidUtilities.dp(20));
|
||||
cursor.setCursorWidth(1.5f);
|
||||
cursor.setSingleLine(true);
|
||||
cursor.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL);
|
||||
cursor.setHeaderHintColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueHeader));
|
||||
cursor.setTransformHintToHeader(true);
|
||||
cursor.setLineColors(Theme.getColor(Theme.key_windowBackgroundWhiteInputField), Theme.getColor(Theme.key_windowBackgroundWhiteInputFieldActivated), Theme.getColor(Theme.key_windowBackgroundWhiteRedText3));
|
||||
return cursor;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) {
|
||||
if (isOpen && !backward && currentProxyInfo == null) {
|
||||
serverField.requestFocus();
|
||||
AndroidUtilities.showKeyboard(serverField);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<ThemeDescription> getThemeDescriptions() {
|
||||
final ThemeDescription.ThemeDescriptionDelegate delegate = () -> {
|
||||
if (inputFields != null) {
|
||||
for (int i = 0; i < inputFields.length; i++) {
|
||||
inputFields[i].setLineColors(Theme.getColor(Theme.key_windowBackgroundWhiteInputField),
|
||||
Theme.getColor(Theme.key_windowBackgroundWhiteInputFieldActivated),
|
||||
Theme.getColor(Theme.key_windowBackgroundWhiteRedText3));
|
||||
}
|
||||
}
|
||||
};
|
||||
ArrayList<ThemeDescription> arrayList = new ArrayList<>();
|
||||
arrayList.add(new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault));
|
||||
arrayList.add(new ThemeDescription(scrollView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCH, null, null, null, null, Theme.key_actionBarDefaultSearch));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCHPLACEHOLDER, null, null, null, null, Theme.key_actionBarDefaultSearchPlaceholder));
|
||||
arrayList.add(new ThemeDescription(inputFieldsContainer, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite));
|
||||
arrayList.add(new ThemeDescription(linearLayout2, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider));
|
||||
|
||||
arrayList.add(new ThemeDescription(null, 0, null, null, null, null, delegate, Theme.key_windowBackgroundWhiteBlueText4));
|
||||
arrayList.add(new ThemeDescription(null, 0, null, null, null, null, delegate, Theme.key_windowBackgroundWhiteGrayText2));
|
||||
|
||||
|
||||
if (inputFields != null) {
|
||||
for (int a = 0; a < inputFields.length; a++) {
|
||||
arrayList.add(new ThemeDescription(inputFields[a], ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
|
||||
arrayList.add(new ThemeDescription(inputFields[a], ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText));
|
||||
arrayList.add(new ThemeDescription(inputFields[a], ThemeDescription.FLAG_HINTTEXTCOLOR | ThemeDescription.FLAG_PROGRESSBAR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader));
|
||||
arrayList.add(new ThemeDescription(inputFields[a], ThemeDescription.FLAG_CURSORCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
|
||||
arrayList.add(new ThemeDescription(null, 0, null, null, null, delegate, Theme.key_windowBackgroundWhiteInputField));
|
||||
arrayList.add(new ThemeDescription(null, 0, null, null, null, delegate, Theme.key_windowBackgroundWhiteInputFieldActivated));
|
||||
arrayList.add(new ThemeDescription(null, 0, null, null, null, delegate, Theme.key_windowBackgroundWhiteRedText3));
|
||||
}
|
||||
} else {
|
||||
arrayList.add(new ThemeDescription(null, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
|
||||
arrayList.add(new ThemeDescription(null, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText));
|
||||
}
|
||||
|
||||
arrayList.add(new ThemeDescription(bottomCell, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow));
|
||||
arrayList.add(new ThemeDescription(bottomCell, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4));
|
||||
arrayList.add(new ThemeDescription(bottomCell, ThemeDescription.FLAG_LINKCOLOR, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteLinkText));
|
||||
|
||||
return arrayList;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,259 @@
|
|||
package tw.nekomimi.nekogram
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import cn.hutool.core.codec.Base64
|
||||
import com.github.shadowsocks.plugin.PluginConfiguration
|
||||
import com.github.shadowsocks.plugin.PluginManager
|
||||
import com.v2ray.ang.V2RayConfig.SS_PROTOCOL
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import org.json.JSONObject
|
||||
import org.telegram.messenger.ApplicationLoader
|
||||
import org.telegram.messenger.FileLog
|
||||
import tw.nekomimi.nekogram.utils.AlertUtil
|
||||
import tw.nekomimi.nekogram.utils.FileUtil
|
||||
import java.io.File
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
class ShadowsocksLoader {
|
||||
|
||||
lateinit var bean: Bean
|
||||
var port by Delegates.notNull<Int>()
|
||||
var shadowsocksProcess: GuardedProcessPool? = null
|
||||
|
||||
fun initConfig(bean: Bean, port: Int) {
|
||||
|
||||
this.bean = bean
|
||||
this.port = port
|
||||
|
||||
}
|
||||
|
||||
fun start() {
|
||||
|
||||
stop()
|
||||
|
||||
val cacheCfg = File(ApplicationLoader.applicationContext.cacheDir, "ss_cfg_${bean.hash}.json")
|
||||
|
||||
shadowsocksProcess = GuardedProcessPool {
|
||||
|
||||
FileLog.e(it)
|
||||
|
||||
}.apply {
|
||||
|
||||
runCatching {
|
||||
|
||||
cacheCfg.writeText(bean.toJson().toString())
|
||||
|
||||
start(listOf(FileUtil.extLib("ss-local").path,
|
||||
"--local-addr", "127.0.0.1:$port",
|
||||
"--config", cacheCfg.path)) {
|
||||
|
||||
cacheCfg.delete()
|
||||
|
||||
}
|
||||
|
||||
}.onFailure { it ->
|
||||
|
||||
AlertUtil.showToast("${it.javaClass.simpleName}: ${it.message}")
|
||||
|
||||
cacheCfg.delete()
|
||||
|
||||
FileLog.e(it)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
|
||||
if (shadowsocksProcess != null) {
|
||||
|
||||
val proc = shadowsocksProcess!!
|
||||
|
||||
thread {
|
||||
|
||||
runCatching {
|
||||
|
||||
runBlocking { proc.close(this) }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
shadowsocksProcess = null
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data class Bean(
|
||||
var host: String = "",
|
||||
var remotePort: Int = 443,
|
||||
var password: String = "",
|
||||
var method: String = "aes-256-cfb",
|
||||
var plugin: String = "",
|
||||
var remarks: String? = null
|
||||
) {
|
||||
|
||||
init {
|
||||
|
||||
if (method == "plain") method = "none"
|
||||
|
||||
val pl = PluginConfiguration(plugin)
|
||||
|
||||
if (pl.selected.contains("v2ray") && pl.selected != "v2ray") {
|
||||
|
||||
pl.pluginsOptions["v2ray"] = pl.getOptions()
|
||||
pl.pluginsOptions.remove(pl.selected)
|
||||
pl.selected = "v2ray"
|
||||
|
||||
// reslove v2ray plugin
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return super.equals(other) || (other is Bean && hash == other.hash)
|
||||
}
|
||||
|
||||
/*
|
||||
init {
|
||||
|
||||
if (method !in methods) error("method $method not supported")
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
val hash = (host + remotePort + password + method).hashCode()
|
||||
|
||||
val pluginInitResult by lazy {
|
||||
PluginManager.init(PluginConfiguration(plugin ?: ""))
|
||||
}
|
||||
|
||||
fun toJson(): JSONObject = JSONObject().apply {
|
||||
put("server", host)
|
||||
put("server_port", remotePort)
|
||||
put("password", password)
|
||||
put("method", method)
|
||||
put("ipv6", true)
|
||||
if (pluginInitResult != null) {
|
||||
put("plugin", pluginInitResult!!.first)
|
||||
put("plugin_opts", pluginInitResult!!.second.toString())
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun parse(url: String): Bean {
|
||||
|
||||
if (url.contains("@")) {
|
||||
|
||||
// ss-android style
|
||||
|
||||
val link = url.replace(SS_PROTOCOL, "https://").toHttpUrlOrNull()
|
||||
?: error("invalid ss-android link $url")
|
||||
|
||||
if (link.password.isNotBlank()) {
|
||||
|
||||
return Bean(
|
||||
link.host,
|
||||
link.port,
|
||||
link.password,
|
||||
link.username,
|
||||
link.queryParameter("plugin") ?: "",
|
||||
link.fragment
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
val methodAndPswd = Base64.decodeStr(link.username)
|
||||
|
||||
return Bean(
|
||||
link.host,
|
||||
link.port,
|
||||
methodAndPswd.substringAfter(":"),
|
||||
methodAndPswd.substringBefore(":"),
|
||||
link.queryParameter("plugin") ?: "",
|
||||
link.fragment
|
||||
)
|
||||
|
||||
} else {
|
||||
|
||||
// v2rayNG style
|
||||
|
||||
var v2Url = url
|
||||
|
||||
if (v2Url.contains("#")) v2Url = v2Url.substringBefore("#")
|
||||
|
||||
val link = ("https://" + Base64.decodeStr(v2Url.substringAfter(SS_PROTOCOL))).toHttpUrlOrNull()
|
||||
?: error("invalid v2rayNG link $url")
|
||||
|
||||
return Bean(
|
||||
link.host,
|
||||
link.port,
|
||||
link.password,
|
||||
link.username,
|
||||
"",
|
||||
link.fragment
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
|
||||
val url = HttpUrl.Builder()
|
||||
.scheme("https")
|
||||
.encodedUsername(Base64.encodeUrlSafe("$method:$password"))
|
||||
.host(host)
|
||||
.port(remotePort)
|
||||
|
||||
if (!remarks.isNullOrBlank()) url.fragment(remarks)
|
||||
|
||||
if (plugin.isNotBlank()) url.addQueryParameter("plugin", plugin)
|
||||
|
||||
return url.build().toString().replace("https://", "ss://")
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val methods = arrayOf(
|
||||
|
||||
"none",
|
||||
"rc4-md5",
|
||||
"aes-128-cfb",
|
||||
"aes-192-cfb",
|
||||
"aes-256-cfb",
|
||||
"aes-128-ctr",
|
||||
"aes-192-ctr",
|
||||
"aes-256-ctr",
|
||||
"bf-cfb",
|
||||
"camellia-128-cfb",
|
||||
"camellia-192-cfb",
|
||||
"camellia-256-cfb",
|
||||
"salsa20",
|
||||
"chacha20",
|
||||
"chacha20-ietf",
|
||||
"aes-128-gcm",
|
||||
"aes-192-gcm",
|
||||
"aes-256-gcm",
|
||||
"chacha20-ietf-poly1305",
|
||||
"xchacha20-ietf-poly1305"
|
||||
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,242 @@
|
|||
package tw.nekomimi.nekogram
|
||||
|
||||
import cn.hutool.core.codec.Base64
|
||||
import com.v2ray.ang.V2RayConfig.SSR_PROTOCOL
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import org.json.JSONObject
|
||||
import org.telegram.messenger.ApplicationLoader
|
||||
import org.telegram.messenger.FileLog
|
||||
import tw.nekomimi.nekogram.utils.FileUtil
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
class ShadowsocksRLoader {
|
||||
|
||||
lateinit var bean: Bean
|
||||
var port by Delegates.notNull<Int>()
|
||||
var shadowsocksProcess: GuardedProcessPool? = null
|
||||
|
||||
fun initConfig(bean: Bean, port: Int) {
|
||||
|
||||
this.bean = bean
|
||||
this.port = port
|
||||
|
||||
}
|
||||
|
||||
fun start() {
|
||||
|
||||
stop()
|
||||
|
||||
val cacheCfg = File(ApplicationLoader.applicationContext.cacheDir, "ssr_cfg_${bean.hash}.json")
|
||||
|
||||
cacheCfg.writeText(bean.toJson().toString())
|
||||
|
||||
shadowsocksProcess = GuardedProcessPool {
|
||||
|
||||
FileLog.e(it)
|
||||
|
||||
}.apply {
|
||||
|
||||
runCatching {
|
||||
|
||||
start(listOf(FileUtil.extLib("ssr-local").path,
|
||||
"-b", "127.0.0.1",
|
||||
"--host", bean.host,
|
||||
"-t", "600",
|
||||
"-c", cacheCfg.path,
|
||||
"-l", port.toString())) {
|
||||
|
||||
cacheCfg.delete()
|
||||
|
||||
}
|
||||
|
||||
}.onFailure {
|
||||
|
||||
cacheCfg.delete()
|
||||
|
||||
FileLog.e(it)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
|
||||
if (shadowsocksProcess != null) {
|
||||
|
||||
val proc = shadowsocksProcess!!
|
||||
|
||||
thread {
|
||||
|
||||
runCatching {
|
||||
|
||||
runBlocking { proc.close(this) }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
shadowsocksProcess = null
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data class Bean(
|
||||
var host: String = "",
|
||||
var remotePort: Int = 443,
|
||||
var password: String = "",
|
||||
var protocol: String = "origin",
|
||||
var protocol_param: String = "",
|
||||
var obfs: String = "plain",
|
||||
var obfs_param: String = "",
|
||||
var method: String = "aes-256-cfb",
|
||||
var remarks: String? = null
|
||||
) {
|
||||
|
||||
val hash = (host + remotePort + password + protocol + obfs + method).hashCode()
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return super.equals(other) || (other is Bean && hash == other.hash)
|
||||
}
|
||||
|
||||
/*
|
||||
init {
|
||||
|
||||
if (method !in methods) error("method $method not supported")
|
||||
if (protocol !in protocols) error("protocol $protocol not supported")
|
||||
if (obfs !in obfses) error("obfs $obfs not supported")
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
fun toJson(): JSONObject = JSONObject().apply {
|
||||
put("server", host)
|
||||
put("server_port", remotePort)
|
||||
put("password", password)
|
||||
put("method", method)
|
||||
put("protocol", protocol)
|
||||
put("protocol_param", protocol_param)
|
||||
put("obfs", obfs)
|
||||
put("obfs_param", obfs_param)
|
||||
put("remarks", remarks)
|
||||
put("route", "all")
|
||||
put("remote_dns", "8.8.8.8:53")
|
||||
put("ipv6", true)
|
||||
put("metered", false)
|
||||
put("proxy_apps", JSONObject().apply {
|
||||
put("enabled", false)
|
||||
})
|
||||
put("udpdns", false)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun parse(url: String): Bean {
|
||||
|
||||
val params = Base64.decodeStr(url.substringAfter(SSR_PROTOCOL)).split(":")
|
||||
|
||||
val bean = Bean(params[0],
|
||||
params[1].toInt(),
|
||||
protocol = params[2],
|
||||
method = params[3],
|
||||
obfs = params[4],
|
||||
password = Base64.decodeStr(params[5].substringBefore("/")))
|
||||
|
||||
val httpUrl = ("https://localhost" + params[5].substringAfter("/")).toHttpUrl()
|
||||
|
||||
runCatching {
|
||||
|
||||
bean.obfs_param = Base64.decodeStr(httpUrl.queryParameter("obfsparam")!!)
|
||||
|
||||
}
|
||||
|
||||
runCatching {
|
||||
|
||||
bean.protocol_param = Base64.decodeStr(httpUrl.queryParameter("protoparam")!!)
|
||||
|
||||
}
|
||||
|
||||
runCatching {
|
||||
|
||||
val remarks = httpUrl.queryParameter("remarks")
|
||||
|
||||
if (remarks?.isNotBlank() == true) {
|
||||
|
||||
bean.remarks = Base64.decodeStr(remarks)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return bean
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
|
||||
return "ssr://" + Base64.encodeUrlSafe("%s:%d:%s:%s:%s:%s/?obfsparam=%s&protoparam=%s&remarks=%s".format(Locale.ENGLISH, host, remotePort, protocol, method, obfs,
|
||||
Base64.encodeUrlSafe("%s".format(Locale.ENGLISH, password)),
|
||||
Base64.encodeUrlSafe("%s".format(Locale.ENGLISH, obfs_param)),
|
||||
Base64.encodeUrlSafe("%s".format(Locale.ENGLISH, protocol_param)),
|
||||
Base64.encodeUrlSafe("%s".format(Locale.ENGLISH, remarks ?: ""))))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val methods = arrayOf(
|
||||
|
||||
"none",
|
||||
"table",
|
||||
"rc4",
|
||||
"rc4-md5",
|
||||
"rc4-md5-6",
|
||||
"aes-128-cfb",
|
||||
"aes-192-cfb",
|
||||
"aes-256-cfb",
|
||||
"aes-128-ctr",
|
||||
"aes-192-ctr",
|
||||
"aes-256-ctr",
|
||||
"bf-cfb",
|
||||
"camellia-128-cfb",
|
||||
"camellia-192-cfb",
|
||||
"camellia-256-cfb",
|
||||
"salsa20",
|
||||
"chacha20",
|
||||
"chacha20-ietf"
|
||||
|
||||
)
|
||||
|
||||
val protocols = arrayOf(
|
||||
"origin",
|
||||
"verify_simple",
|
||||
"verify_sha1",
|
||||
"auth_sha1",
|
||||
"auth_sha1_v2",
|
||||
"auth_sha1_v4",
|
||||
"auth_aes128_sha1",
|
||||
"auth_aes128_md5",
|
||||
"auth_chain_a",
|
||||
"auth_chain_b"
|
||||
)
|
||||
|
||||
val obfses = arrayOf(
|
||||
"plain",
|
||||
"http_simple",
|
||||
"http_post",
|
||||
"tls_simple",
|
||||
"tls1.2_ticket_auth"
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,469 @@
|
|||
/*
|
||||
* This is the source code of Telegram for Android v. 5.x.x.
|
||||
* It is licensed under GNU GPL v. 2 or later.
|
||||
* You should have received a copy of the license in this archive (see LICENSE).
|
||||
*
|
||||
* Copyright Nikolai Kudashov, 2013-2018.
|
||||
*/
|
||||
|
||||
package tw.nekomimi.nekogram;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.os.Build;
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.telegram.messenger.AndroidUtilities;
|
||||
import org.telegram.messenger.LocaleController;
|
||||
import org.telegram.messenger.R;
|
||||
import org.telegram.messenger.SharedConfig;
|
||||
import org.telegram.messenger.Utilities;
|
||||
import org.telegram.ui.ActionBar.ActionBar;
|
||||
import org.telegram.ui.ActionBar.ActionBarMenuItem;
|
||||
import org.telegram.ui.ActionBar.BaseFragment;
|
||||
import org.telegram.ui.ActionBar.Theme;
|
||||
import org.telegram.ui.ActionBar.ThemeDescription;
|
||||
import org.telegram.ui.Cells.TextInfoPrivacyCell;
|
||||
import org.telegram.ui.Cells.TextSettingsCell;
|
||||
import org.telegram.ui.Components.EditTextBoldCursor;
|
||||
import org.telegram.ui.Components.LayoutHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import kotlin.Unit;
|
||||
import tw.nekomimi.nekogram.utils.PopupBuilder;
|
||||
|
||||
public class ShadowsocksRSettingsActivity extends BaseFragment {
|
||||
|
||||
private EditTextBoldCursor[] inputFields;
|
||||
|
||||
private EditTextBoldCursor ipField;
|
||||
private EditTextBoldCursor portField;
|
||||
private EditTextBoldCursor passwordField;
|
||||
private TextSettingsCell methodField;
|
||||
|
||||
private TextSettingsCell protocolField;
|
||||
private EditTextBoldCursor protocolParamField;
|
||||
|
||||
private TextSettingsCell obfsField;
|
||||
private EditTextBoldCursor obfsParamField;
|
||||
|
||||
private EditTextBoldCursor remarksField;
|
||||
|
||||
private ScrollView scrollView;
|
||||
private LinearLayout linearLayout2;
|
||||
private LinearLayout inputFieldsContainer;
|
||||
|
||||
private TextInfoPrivacyCell bottomCell;
|
||||
|
||||
private SharedConfig.ShadowsocksRProxy currentProxyInfo;
|
||||
private ShadowsocksRLoader.Bean currentBean;
|
||||
|
||||
private boolean ignoreOnTextChange;
|
||||
|
||||
private static final int done_button = 1;
|
||||
|
||||
public class TypeCell extends FrameLayout {
|
||||
|
||||
private TextView textView;
|
||||
private ImageView checkImage;
|
||||
private boolean needDivider;
|
||||
|
||||
public TypeCell(Context context) {
|
||||
super(context);
|
||||
|
||||
setWillNotDraw(false);
|
||||
|
||||
textView = new TextView(context);
|
||||
textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));
|
||||
textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||
textView.setLines(1);
|
||||
textView.setMaxLines(1);
|
||||
textView.setSingleLine(true);
|
||||
textView.setEllipsize(TextUtils.TruncateAt.END);
|
||||
textView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL);
|
||||
addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 23 + 48 : 21, 0, LocaleController.isRTL ? 21 : 23, 0));
|
||||
|
||||
checkImage = new ImageView(context);
|
||||
checkImage.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_featuredStickers_addedIcon), PorterDuff.Mode.SRC_IN));
|
||||
checkImage.setImageResource(R.drawable.sticker_added);
|
||||
addView(checkImage, LayoutHelper.createFrame(19, 14, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.CENTER_VERTICAL, 21, 0, 21, 0));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(50) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY));
|
||||
}
|
||||
|
||||
public void setValue(String name, boolean checked, boolean divider) {
|
||||
textView.setText(name);
|
||||
checkImage.setVisibility(checked ? VISIBLE : INVISIBLE);
|
||||
needDivider = divider;
|
||||
}
|
||||
|
||||
public void setTypeChecked(boolean value) {
|
||||
checkImage.setVisibility(value ? VISIBLE : INVISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
if (needDivider) {
|
||||
canvas.drawLine(LocaleController.isRTL ? 0 : AndroidUtilities.dp(20), getMeasuredHeight() - 1, getMeasuredWidth() - (LocaleController.isRTL ? AndroidUtilities.dp(20) : 0), getMeasuredHeight() - 1, Theme.dividerPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ShadowsocksRSettingsActivity() {
|
||||
super();
|
||||
currentBean = new ShadowsocksRLoader.Bean();
|
||||
}
|
||||
|
||||
public ShadowsocksRSettingsActivity(SharedConfig.ShadowsocksRProxy proxyInfo) {
|
||||
super();
|
||||
currentProxyInfo = proxyInfo;
|
||||
currentBean = proxyInfo.bean;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
AndroidUtilities.requestAdjustResize(getParentActivity(), classGuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View createView(Context context) {
|
||||
actionBar.setTitle(LocaleController.getString("ProxyDetails", R.string.ProxyDetails));
|
||||
actionBar.setBackButtonImage(R.drawable.ic_ab_back);
|
||||
actionBar.setAllowOverlayTitle(false);
|
||||
if (AndroidUtilities.isTablet()) {
|
||||
actionBar.setOccupyStatusBar(false);
|
||||
}
|
||||
|
||||
actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() {
|
||||
@Override
|
||||
public void onItemClick(int id) {
|
||||
if (id == -1) {
|
||||
finishFragment();
|
||||
} else if (id == done_button) {
|
||||
|
||||
if (getParentActivity() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(ipField.getText())) {
|
||||
|
||||
ipField.requestFocus();
|
||||
AndroidUtilities.showKeyboard(ipField);
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(portField.getText())) {
|
||||
|
||||
portField.requestFocus();
|
||||
AndroidUtilities.showKeyboard(portField);
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(passwordField.getText())) {
|
||||
|
||||
passwordField.requestFocus();
|
||||
AndroidUtilities.showKeyboard(passwordField);
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
currentBean.setHost(ipField.getText().toString());
|
||||
currentBean.setRemotePort(Utilities.parseInt(portField.getText().toString()));
|
||||
currentBean.setPassword(passwordField.getText().toString());
|
||||
currentBean.setMethod(methodField.getValueTextView().getText().toString());
|
||||
currentBean.setProtocol(protocolField.getValueTextView().getText().toString());
|
||||
currentBean.setProtocol_param(protocolParamField.getText().toString());
|
||||
currentBean.setObfs(obfsField.getValueTextView().getText().toString());
|
||||
currentBean.setObfs_param(obfsParamField.getText().toString());
|
||||
currentBean.setRemarks(remarksField.getText().toString());
|
||||
|
||||
if (currentProxyInfo == null) {
|
||||
currentProxyInfo = new SharedConfig.ShadowsocksRProxy(currentBean);
|
||||
SharedConfig.addProxy(currentProxyInfo);
|
||||
SharedConfig.setCurrentProxy(currentProxyInfo);
|
||||
} else {
|
||||
currentProxyInfo.proxyCheckPingId = 0;
|
||||
currentProxyInfo.availableCheckTime = 0;
|
||||
currentProxyInfo.ping = 0;
|
||||
SharedConfig.saveProxyList();
|
||||
SharedConfig.setProxyEnable(false);
|
||||
}
|
||||
|
||||
finishFragment();
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ActionBarMenuItem doneItem = actionBar.createMenu().addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56));
|
||||
doneItem.setContentDescription(LocaleController.getString("Done", R.string.Done));
|
||||
|
||||
fragmentView = new FrameLayout(context);
|
||||
FrameLayout frameLayout = (FrameLayout) fragmentView;
|
||||
fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray));
|
||||
|
||||
scrollView = new ScrollView(context);
|
||||
scrollView.setFillViewport(true);
|
||||
AndroidUtilities.setScrollViewEdgeEffectColor(scrollView, Theme.getColor(Theme.key_actionBarDefault));
|
||||
frameLayout.addView(scrollView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT));
|
||||
|
||||
linearLayout2 = new LinearLayout(context);
|
||||
linearLayout2.setOrientation(LinearLayout.VERTICAL);
|
||||
scrollView.addView(linearLayout2, new ScrollView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
inputFieldsContainer = new LinearLayout(context);
|
||||
inputFieldsContainer.setOrientation(LinearLayout.VERTICAL);
|
||||
inputFieldsContainer.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite));
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
// bring to front for transitions
|
||||
inputFieldsContainer.setElevation(AndroidUtilities.dp(1f));
|
||||
inputFieldsContainer.setOutlineProvider(null);
|
||||
}
|
||||
linearLayout2.addView(inputFieldsContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
|
||||
|
||||
inputFields = new EditTextBoldCursor[6];
|
||||
|
||||
for (int a = 0; a < 6; a++) {
|
||||
FrameLayout container = new FrameLayout(context);
|
||||
EditTextBoldCursor cursor = mkCursor();
|
||||
inputFields[a] = cursor;
|
||||
cursor.setImeOptions(EditorInfo.IME_ACTION_NEXT | EditorInfo.IME_FLAG_NO_EXTRACT_UI);
|
||||
switch (a) {
|
||||
case 0:
|
||||
ipField = cursor;
|
||||
cursor.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
cursor.setHintText(LocaleController.getString("UseProxyAddress", R.string.UseProxyAddress));
|
||||
cursor.setText(currentBean.getHost());
|
||||
break;
|
||||
case 1:
|
||||
portField = cursor;
|
||||
cursor.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||
cursor.setHintText(LocaleController.getString("UseProxyPort", R.string.UseProxyPort));
|
||||
cursor.setText("" + currentBean.getRemotePort());
|
||||
break;
|
||||
case 2:
|
||||
passwordField = cursor;
|
||||
cursor.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
cursor.setHintText(LocaleController.getString("UseProxyPassword", R.string.UseProxyPassword));
|
||||
cursor.setText(currentBean.getPassword());
|
||||
break;
|
||||
case 3:
|
||||
protocolParamField = cursor;
|
||||
cursor.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
cursor.setHintText(LocaleController.getString("SSRProtocolParams", R.string.SSRProtocolParams));
|
||||
cursor.setText(currentBean.getProtocol_param());
|
||||
break;
|
||||
case 4:
|
||||
obfsParamField = cursor;
|
||||
cursor.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
cursor.setHintText(LocaleController.getString("SSRObfsParam", R.string.SSRObfsParam));
|
||||
cursor.setText(currentBean.getObfs_param());
|
||||
break;
|
||||
case 5:
|
||||
remarksField = cursor;
|
||||
cursor.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
cursor.setHintText(LocaleController.getString("ProxyRemarks", R.string.ProxyRemarks));
|
||||
cursor.setText(currentBean.getRemarks());
|
||||
break;
|
||||
}
|
||||
cursor.setSelection(cursor.length());
|
||||
|
||||
cursor.setPadding(0, 0, 0, 0);
|
||||
container.addView(cursor, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 17, a == 0 ? 12 : 0, 17, 0));
|
||||
|
||||
}
|
||||
|
||||
inputFieldsContainer.addView((View) ipField.getParent(), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
inputFieldsContainer.addView((View) portField.getParent(), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
inputFieldsContainer.addView((View) passwordField.getParent(), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
|
||||
FrameLayout container = new FrameLayout(context);
|
||||
|
||||
methodField = new TextSettingsCell(context);
|
||||
methodField.setBackground(Theme.getSelectorDrawable(false));
|
||||
methodField.setTextAndValue(LocaleController.getString("SSMethod", R.string.SSMethod), currentBean.getMethod(), false);
|
||||
container.addView(methodField, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 0, 0, 0, 0));
|
||||
|
||||
methodField.setOnClickListener((v) -> {
|
||||
|
||||
PopupBuilder select = new PopupBuilder(v);
|
||||
|
||||
select.setItems(ShadowsocksRLoader.Companion.getMethods(), (__,value) -> {
|
||||
|
||||
methodField.getValueTextView().setText(value);
|
||||
|
||||
return Unit.INSTANCE;
|
||||
|
||||
});
|
||||
|
||||
select.show();
|
||||
|
||||
});
|
||||
|
||||
inputFieldsContainer.addView(container, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
|
||||
container = new FrameLayout(context);
|
||||
protocolField = new TextSettingsCell(context);
|
||||
protocolField.setBackground(Theme.getSelectorDrawable(false));
|
||||
protocolField.setTextAndValue(LocaleController.getString("SSRProtocol", R.string.SSRProtocol), currentBean.getProtocol(), false);
|
||||
container.addView(protocolField, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 0, 0, 0, 0));
|
||||
|
||||
protocolField.setOnClickListener((v) -> {
|
||||
|
||||
PopupBuilder select = new PopupBuilder(v);
|
||||
|
||||
select.setItems(ShadowsocksRLoader.Companion.getProtocols(), (__,value) -> {
|
||||
|
||||
protocolField.getValueTextView().setText(value);
|
||||
|
||||
return Unit.INSTANCE;
|
||||
|
||||
});
|
||||
|
||||
select.show();
|
||||
|
||||
});
|
||||
|
||||
inputFieldsContainer.addView(container, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
|
||||
inputFieldsContainer.addView((View) protocolParamField.getParent(), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
|
||||
container = new FrameLayout(context);
|
||||
obfsField = new TextSettingsCell(context);
|
||||
obfsField.setBackground(Theme.getSelectorDrawable(false));
|
||||
obfsField.setTextAndValue(LocaleController.getString("SSRObfs", R.string.SSRObfs), currentBean.getObfs(), false);
|
||||
container.addView(obfsField, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 0, 0, 0, 0));
|
||||
|
||||
obfsField.setOnClickListener((v) -> {
|
||||
|
||||
PopupBuilder select = new PopupBuilder(v);
|
||||
|
||||
select.setItems(ShadowsocksRLoader.Companion.getObfses(), (__,value) -> {
|
||||
|
||||
obfsField.getValueTextView().setText(value);
|
||||
|
||||
return Unit.INSTANCE;
|
||||
|
||||
});
|
||||
|
||||
select.show();
|
||||
|
||||
});
|
||||
|
||||
inputFieldsContainer.addView(container, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
|
||||
inputFieldsContainer.addView((View) obfsParamField.getParent(), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
|
||||
inputFieldsContainer.addView((View) remarksField.getParent(), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
|
||||
bottomCell = new TextInfoPrivacyCell(context);
|
||||
bottomCell.setBackground(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow));
|
||||
bottomCell.setText(LocaleController.getString("ProxyInfoSSR", R.string.ProxyInfoSSR));
|
||||
linearLayout2.addView(bottomCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
|
||||
|
||||
return fragmentView;
|
||||
|
||||
}
|
||||
|
||||
EditTextBoldCursor mkCursor() {
|
||||
|
||||
EditTextBoldCursor cursor = new EditTextBoldCursor(getParentActivity());
|
||||
cursor.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||
cursor.setHintColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText));
|
||||
cursor.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));
|
||||
cursor.setBackground(null);
|
||||
cursor.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));
|
||||
cursor.setCursorSize(AndroidUtilities.dp(20));
|
||||
cursor.setCursorWidth(1.5f);
|
||||
cursor.setSingleLine(true);
|
||||
cursor.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL);
|
||||
cursor.setHeaderHintColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueHeader));
|
||||
cursor.setTransformHintToHeader(true);
|
||||
cursor.setLineColors(Theme.getColor(Theme.key_windowBackgroundWhiteInputField), Theme.getColor(Theme.key_windowBackgroundWhiteInputFieldActivated), Theme.getColor(Theme.key_windowBackgroundWhiteRedText3));
|
||||
return cursor;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) {
|
||||
if (isOpen && !backward && currentProxyInfo == null) {
|
||||
ipField.requestFocus();
|
||||
AndroidUtilities.showKeyboard(ipField);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<ThemeDescription> getThemeDescriptions() {
|
||||
final ThemeDescription.ThemeDescriptionDelegate delegate = () -> {
|
||||
if (inputFields != null) {
|
||||
for (int i = 0; i < inputFields.length; i++) {
|
||||
inputFields[i].setLineColors(Theme.getColor(Theme.key_windowBackgroundWhiteInputField),
|
||||
Theme.getColor(Theme.key_windowBackgroundWhiteInputFieldActivated),
|
||||
Theme.getColor(Theme.key_windowBackgroundWhiteRedText3));
|
||||
}
|
||||
}
|
||||
};
|
||||
ArrayList<ThemeDescription> arrayList = new ArrayList<>();
|
||||
arrayList.add(new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault));
|
||||
arrayList.add(new ThemeDescription(scrollView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCH, null, null, null, null, Theme.key_actionBarDefaultSearch));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCHPLACEHOLDER, null, null, null, null, Theme.key_actionBarDefaultSearchPlaceholder));
|
||||
arrayList.add(new ThemeDescription(inputFieldsContainer, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite));
|
||||
arrayList.add(new ThemeDescription(linearLayout2, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider));
|
||||
|
||||
arrayList.add(new ThemeDescription(null, 0, null, null, null, null, delegate, Theme.key_windowBackgroundWhiteBlueText4));
|
||||
arrayList.add(new ThemeDescription(null, 0, null, null, null, null, delegate, Theme.key_windowBackgroundWhiteGrayText2));
|
||||
|
||||
|
||||
if (inputFields != null) {
|
||||
for (int a = 0; a < inputFields.length; a++) {
|
||||
arrayList.add(new ThemeDescription(inputFields[a], ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
|
||||
arrayList.add(new ThemeDescription(inputFields[a], ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText));
|
||||
arrayList.add(new ThemeDescription(inputFields[a], ThemeDescription.FLAG_HINTTEXTCOLOR | ThemeDescription.FLAG_PROGRESSBAR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader));
|
||||
arrayList.add(new ThemeDescription(inputFields[a], ThemeDescription.FLAG_CURSORCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
|
||||
arrayList.add(new ThemeDescription(null, 0, null, null, null, delegate, Theme.key_windowBackgroundWhiteInputField));
|
||||
arrayList.add(new ThemeDescription(null, 0, null, null, null, delegate, Theme.key_windowBackgroundWhiteInputFieldActivated));
|
||||
arrayList.add(new ThemeDescription(null, 0, null, null, null, delegate, Theme.key_windowBackgroundWhiteRedText3));
|
||||
}
|
||||
} else {
|
||||
arrayList.add(new ThemeDescription(null, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
|
||||
arrayList.add(new ThemeDescription(null, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText));
|
||||
}
|
||||
|
||||
arrayList.add(new ThemeDescription(bottomCell, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow));
|
||||
arrayList.add(new ThemeDescription(bottomCell, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4));
|
||||
arrayList.add(new ThemeDescription(bottomCell, ThemeDescription.FLAG_LINKCOLOR, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteLinkText));
|
||||
|
||||
return arrayList;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,570 @@
|
|||
/*
|
||||
* This is the source code of Telegram for Android v. 5.x.x.
|
||||
* It is licensed under GNU GPL v. 2 or later.
|
||||
* You should have received a copy of the license in this archive (see LICENSE).
|
||||
*
|
||||
* Copyright Nikolai Kudashov, 2013-2018.
|
||||
*/
|
||||
|
||||
package tw.nekomimi.nekogram;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.os.Build;
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.github.shadowsocks.plugin.PluginConfiguration;
|
||||
import com.github.shadowsocks.plugin.PluginContract;
|
||||
import com.github.shadowsocks.plugin.PluginList;
|
||||
import com.github.shadowsocks.plugin.PluginManager;
|
||||
import com.github.shadowsocks.plugin.PluginOptions;
|
||||
|
||||
import org.telegram.messenger.AndroidUtilities;
|
||||
import org.telegram.messenger.LocaleController;
|
||||
import org.telegram.messenger.R;
|
||||
import org.telegram.messenger.SharedConfig;
|
||||
import org.telegram.messenger.Utilities;
|
||||
import org.telegram.ui.ActionBar.ActionBar;
|
||||
import org.telegram.ui.ActionBar.ActionBarMenuItem;
|
||||
import org.telegram.ui.ActionBar.BaseFragment;
|
||||
import org.telegram.ui.ActionBar.Theme;
|
||||
import org.telegram.ui.ActionBar.ThemeDescription;
|
||||
import org.telegram.ui.Cells.TextInfoPrivacyCell;
|
||||
import org.telegram.ui.Cells.TextSettingsCell;
|
||||
import org.telegram.ui.Components.EditTextBoldCursor;
|
||||
import org.telegram.ui.Components.LayoutHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import kotlin.Unit;
|
||||
import tw.nekomimi.nekogram.utils.AlertUtil;
|
||||
import tw.nekomimi.nekogram.utils.PopupBuilder;
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
public class ShadowsocksSettingsActivity extends BaseFragment {
|
||||
|
||||
private EditTextBoldCursor[] inputFields;
|
||||
|
||||
private EditTextBoldCursor ipField;
|
||||
private EditTextBoldCursor portField;
|
||||
private EditTextBoldCursor passwordField;
|
||||
private TextSettingsCell methodField;
|
||||
private TextSettingsCell pluginField;
|
||||
private TextSettingsCell pluginOptsField;
|
||||
private EditTextBoldCursor remarksField;
|
||||
|
||||
private ScrollView scrollView;
|
||||
private LinearLayout linearLayout2;
|
||||
private LinearLayout inputFieldsContainer;
|
||||
|
||||
private TextInfoPrivacyCell bottomCell;
|
||||
|
||||
private SharedConfig.ShadowsocksProxy currentProxyInfo;
|
||||
private ShadowsocksLoader.Bean currentBean;
|
||||
private PluginConfiguration plugin;
|
||||
|
||||
private boolean ignoreOnTextChange;
|
||||
|
||||
private static final int done_button = 1;
|
||||
|
||||
public class TypeCell extends FrameLayout {
|
||||
|
||||
private TextView textView;
|
||||
private ImageView checkImage;
|
||||
private boolean needDivider;
|
||||
|
||||
public TypeCell(Context context) {
|
||||
super(context);
|
||||
|
||||
setWillNotDraw(false);
|
||||
|
||||
textView = new TextView(context);
|
||||
textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));
|
||||
textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||
textView.setLines(1);
|
||||
textView.setMaxLines(1);
|
||||
textView.setSingleLine(true);
|
||||
textView.setEllipsize(TextUtils.TruncateAt.END);
|
||||
textView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL);
|
||||
addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 23 + 48 : 21, 0, LocaleController.isRTL ? 21 : 23, 0));
|
||||
|
||||
checkImage = new ImageView(context);
|
||||
checkImage.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_featuredStickers_addedIcon), PorterDuff.Mode.SRC_IN));
|
||||
checkImage.setImageResource(R.drawable.sticker_added);
|
||||
addView(checkImage, LayoutHelper.createFrame(19, 14, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.CENTER_VERTICAL, 21, 0, 21, 0));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(50) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY));
|
||||
}
|
||||
|
||||
public void setValue(String name, boolean checked, boolean divider) {
|
||||
textView.setText(name);
|
||||
checkImage.setVisibility(checked ? VISIBLE : INVISIBLE);
|
||||
needDivider = divider;
|
||||
}
|
||||
|
||||
public void setTypeChecked(boolean value) {
|
||||
checkImage.setVisibility(value ? VISIBLE : INVISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
if (needDivider) {
|
||||
canvas.drawLine(LocaleController.isRTL ? 0 : AndroidUtilities.dp(20), getMeasuredHeight() - 1, getMeasuredWidth() - (LocaleController.isRTL ? AndroidUtilities.dp(20) : 0), getMeasuredHeight() - 1, Theme.dividerPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ShadowsocksSettingsActivity() {
|
||||
super();
|
||||
currentBean = new ShadowsocksLoader.Bean();
|
||||
plugin = new PluginConfiguration("");
|
||||
}
|
||||
|
||||
public ShadowsocksSettingsActivity(SharedConfig.ShadowsocksProxy proxyInfo) {
|
||||
super();
|
||||
currentProxyInfo = proxyInfo;
|
||||
currentBean = proxyInfo.bean;
|
||||
plugin = new PluginConfiguration(currentBean.getPlugin());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
AndroidUtilities.requestAdjustResize(getParentActivity(), classGuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View createView(Context context) {
|
||||
actionBar.setTitle(LocaleController.getString("ProxyDetails", R.string.ProxyDetails));
|
||||
actionBar.setBackButtonImage(R.drawable.ic_ab_back);
|
||||
actionBar.setAllowOverlayTitle(false);
|
||||
if (AndroidUtilities.isTablet()) {
|
||||
actionBar.setOccupyStatusBar(false);
|
||||
}
|
||||
|
||||
actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() {
|
||||
@Override
|
||||
public void onItemClick(int id) {
|
||||
if (id == -1) {
|
||||
finishFragment();
|
||||
} else if (id == done_button) {
|
||||
|
||||
if (getParentActivity() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(ipField.getText())) {
|
||||
|
||||
ipField.requestFocus();
|
||||
AndroidUtilities.showKeyboard(ipField);
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(portField.getText())) {
|
||||
|
||||
portField.requestFocus();
|
||||
AndroidUtilities.showKeyboard(portField);
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(passwordField.getText()) && !"plain".equals(methodField.getTextView().getText().toString().toLowerCase())) {
|
||||
|
||||
passwordField.requestFocus();
|
||||
AndroidUtilities.showKeyboard(passwordField);
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
currentBean.setHost(ipField.getText().toString());
|
||||
currentBean.setRemotePort(Utilities.parseInt(portField.getText().toString()));
|
||||
currentBean.setPassword(passwordField.getText().toString());
|
||||
currentBean.setMethod(methodField.getValueTextView().getText().toString());
|
||||
currentBean.setPlugin(plugin.toString());
|
||||
currentBean.setRemarks(remarksField.getText().toString());
|
||||
|
||||
if (currentProxyInfo == null) {
|
||||
currentProxyInfo = new SharedConfig.ShadowsocksProxy(currentBean);
|
||||
SharedConfig.addProxy(currentProxyInfo);
|
||||
SharedConfig.setCurrentProxy(currentProxyInfo);
|
||||
} else {
|
||||
currentProxyInfo.proxyCheckPingId = 0;
|
||||
currentProxyInfo.availableCheckTime = 0;
|
||||
currentProxyInfo.ping = 0;
|
||||
SharedConfig.saveProxyList();
|
||||
SharedConfig.setProxyEnable(false);
|
||||
}
|
||||
|
||||
finishFragment();
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ActionBarMenuItem doneItem = actionBar.createMenu().addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56));
|
||||
doneItem.setContentDescription(LocaleController.getString("Done", R.string.Done));
|
||||
|
||||
fragmentView = new FrameLayout(context);
|
||||
FrameLayout frameLayout = (FrameLayout) fragmentView;
|
||||
fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray));
|
||||
|
||||
scrollView = new ScrollView(context);
|
||||
scrollView.setFillViewport(true);
|
||||
AndroidUtilities.setScrollViewEdgeEffectColor(scrollView, Theme.getColor(Theme.key_actionBarDefault));
|
||||
frameLayout.addView(scrollView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT));
|
||||
|
||||
linearLayout2 = new LinearLayout(context);
|
||||
linearLayout2.setOrientation(LinearLayout.VERTICAL);
|
||||
scrollView.addView(linearLayout2, new ScrollView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
inputFieldsContainer = new LinearLayout(context);
|
||||
inputFieldsContainer.setOrientation(LinearLayout.VERTICAL);
|
||||
inputFieldsContainer.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite));
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
// bring to front for transitions
|
||||
inputFieldsContainer.setElevation(AndroidUtilities.dp(1f));
|
||||
inputFieldsContainer.setOutlineProvider(null);
|
||||
}
|
||||
linearLayout2.addView(inputFieldsContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
|
||||
|
||||
inputFields = new EditTextBoldCursor[4];
|
||||
|
||||
for (int a = 0; a < 4; a++) {
|
||||
FrameLayout container = new FrameLayout(context);
|
||||
EditTextBoldCursor cursor = mkCursor();
|
||||
inputFields[a] = cursor;
|
||||
cursor.setImeOptions(EditorInfo.IME_ACTION_NEXT | EditorInfo.IME_FLAG_NO_EXTRACT_UI);
|
||||
switch (a) {
|
||||
case 0:
|
||||
ipField = cursor;
|
||||
cursor.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
cursor.setHintText(LocaleController.getString("UseProxyAddress", R.string.UseProxyAddress));
|
||||
cursor.setText(currentBean.getHost());
|
||||
break;
|
||||
case 1:
|
||||
portField = cursor;
|
||||
cursor.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||
cursor.setHintText(LocaleController.getString("UseProxyPort", R.string.UseProxyPort));
|
||||
cursor.setText("" + currentBean.getRemotePort());
|
||||
break;
|
||||
case 2:
|
||||
passwordField = cursor;
|
||||
cursor.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
cursor.setHintText(LocaleController.getString("UseProxyPassword", R.string.UseProxyPassword));
|
||||
cursor.setText(currentBean.getPassword());
|
||||
break;
|
||||
case 3:
|
||||
remarksField = cursor;
|
||||
cursor.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
cursor.setHintText(LocaleController.getString("ProxyRemarks", R.string.ProxyRemarks));
|
||||
cursor.setText(currentBean.getRemarks());
|
||||
break;
|
||||
}
|
||||
cursor.setSelection(cursor.length());
|
||||
|
||||
cursor.setPadding(0, 0, 0, 0);
|
||||
container.addView(cursor, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 17, a == 0 ? 12 : 0, 17, 0));
|
||||
|
||||
}
|
||||
|
||||
inputFieldsContainer.addView((View) ipField.getParent(), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
inputFieldsContainer.addView((View) portField.getParent(), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
inputFieldsContainer.addView((View) passwordField.getParent(), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
|
||||
FrameLayout container = new FrameLayout(context);
|
||||
|
||||
methodField = new TextSettingsCell(context);
|
||||
methodField.setBackground(Theme.getSelectorDrawable(false));
|
||||
methodField.setTextAndValue(LocaleController.getString("SSMethod", R.string.SSMethod), currentBean.getMethod(), false);
|
||||
container.addView(methodField, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 0, 0, 0, 0));
|
||||
inputFieldsContainer.addView(container, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
|
||||
methodField.setOnClickListener((v) -> {
|
||||
|
||||
PopupBuilder select = new PopupBuilder(v);
|
||||
|
||||
select.setItems(ShadowsocksLoader.Companion.getMethods(), (__, value) -> {
|
||||
|
||||
methodField.getValueTextView().setText(value);
|
||||
|
||||
return Unit.INSTANCE;
|
||||
|
||||
});
|
||||
|
||||
select.show();
|
||||
|
||||
});
|
||||
|
||||
container = new FrameLayout(context);
|
||||
pluginField = new TextSettingsCell(context);
|
||||
pluginField.setBackground(Theme.getSelectorDrawable(false));
|
||||
pluginField.setTextAndValue(LocaleController.getString("SSPlugin", R.string.SSPlugin), plugin.getSelectedName(), false);
|
||||
container.addView(pluginField, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 0, 0, 0, 0));
|
||||
inputFieldsContainer.addView(container, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
|
||||
pluginField.setOnClickListener((v) -> {
|
||||
|
||||
PluginList plugins = PluginManager.fetchPlugins();
|
||||
|
||||
PopupBuilder select = new PopupBuilder(v);
|
||||
|
||||
try {
|
||||
|
||||
select.setItems(plugins.getLookupNames(), (index, __) -> {
|
||||
|
||||
String pluginId = plugins.get(index).getId();
|
||||
|
||||
plugin.setSelected(pluginId);
|
||||
|
||||
((View) pluginOptsField.getParent()).setVisibility(StrUtil.isBlank(pluginId) ? View.GONE : View.VISIBLE);
|
||||
|
||||
pluginField.getValueTextView().setText(plugin.getSelectedName());
|
||||
pluginOptsField.getValueTextView().setText(plugin.getOptions(pluginId).toString());
|
||||
|
||||
return Unit.INSTANCE;
|
||||
|
||||
});
|
||||
|
||||
select.show();
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
AlertUtil.showSimpleAlert(getParentActivity(),e.getMessage());
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
container = new FrameLayout(context);
|
||||
pluginOptsField = new TextSettingsCell(context);
|
||||
pluginOptsField.setBackground(Theme.getSelectorDrawable(false));
|
||||
pluginOptsField.setTextAndValue(LocaleController.getString("SSPluginOpts", R.string.SSPluginOpts), plugin.getOptions().toString(), false);
|
||||
container.addView(pluginOptsField, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 0, 0, 0, 0));
|
||||
inputFieldsContainer.addView(container, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
|
||||
pluginOptsField.setOnClickListener((v) -> {
|
||||
|
||||
Intent intent = PluginManager.buildIntent(plugin.getSelected(), PluginContract.ACTION_CONFIGURE);
|
||||
intent.putExtra(PluginContract.EXTRA_OPTIONS, plugin.getOptions().toString());
|
||||
|
||||
if (intent.resolveActivity(getParentActivity().getPackageManager()) == null) {
|
||||
showPluginEditor();
|
||||
} else {
|
||||
startActivityForResult(intent, 1919);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
((View) pluginOptsField.getParent()).setVisibility(StrUtil.isBlank(plugin.getSelected()) ? View.GONE : View.VISIBLE);
|
||||
|
||||
inputFieldsContainer.addView((View) remarksField.getParent(), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
|
||||
bottomCell = new TextInfoPrivacyCell(context);
|
||||
bottomCell.setBackground(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow));
|
||||
bottomCell.setText(LocaleController.getString("ProxyInfoSS", R.string.ProxyInfoSS));
|
||||
linearLayout2.addView(bottomCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
|
||||
|
||||
return fragmentView;
|
||||
|
||||
}
|
||||
|
||||
@Override public void onActivityResultFragment(int requestCode, int resultCode, Intent data) {
|
||||
|
||||
if (requestCode == 1919) {
|
||||
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
|
||||
String options = data.getStringExtra(PluginContract.EXTRA_OPTIONS);
|
||||
|
||||
if (options != null) {
|
||||
|
||||
onPreferenceChange(options);
|
||||
|
||||
}
|
||||
|
||||
} else if (resultCode == PluginContract.RESULT_FALLBACK) {
|
||||
|
||||
showPluginEditor();
|
||||
|
||||
}
|
||||
|
||||
} else if (requestCode == 810) {
|
||||
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
|
||||
CharSequence helpMessage = data.getCharSequenceExtra(PluginContract.EXTRA_HELP_MESSAGE);
|
||||
|
||||
if (StrUtil.isBlank(helpMessage)) {
|
||||
|
||||
helpMessage = "No Help :(";
|
||||
|
||||
}
|
||||
|
||||
AlertUtil.showSimpleAlert(getParentActivity(),LocaleController.getString("BotHelp",R.string.BotHelp),helpMessage.toString());
|
||||
|
||||
} else {
|
||||
|
||||
AlertUtil.showSimpleAlert(getParentActivity(),"Get Help Message Error :(");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void showPluginEditor() {
|
||||
|
||||
BottomBuilder builder = new BottomBuilder(getParentActivity());
|
||||
|
||||
builder.addTitle(LocaleController.getString("SSPluginOpts", R.string.SSPluginOpts));
|
||||
|
||||
EditText options = builder.addEditText();
|
||||
options.setSingleLine(false);
|
||||
options.setGravity(Gravity.TOP | LocaleController.generateFlagStart());
|
||||
options.setMinLines(3);
|
||||
options.setText(plugin.getOptions().toString());
|
||||
|
||||
Intent intent = PluginManager.buildIntent(plugin.getSelected(), PluginContract.ACTION_HELP);
|
||||
intent.putExtra(PluginContract.EXTRA_OPTIONS, plugin.getOptions().toString());
|
||||
if (intent.resolveActivity(getParentActivity().getPackageManager()) != null) {
|
||||
|
||||
builder.addButton(LocaleController.getString("BotHelp", R.string.BotHelp), false,true, (it) -> {
|
||||
|
||||
getParentActivity().startActivityForResult(intent, 810);
|
||||
|
||||
return Unit.INSTANCE;
|
||||
|
||||
});
|
||||
|
||||
builder.addCancelButton(false);
|
||||
|
||||
} else {
|
||||
|
||||
builder.addCancelButton();
|
||||
|
||||
}
|
||||
|
||||
builder.addOkButton((it) -> {
|
||||
|
||||
onPreferenceChange(options.getText().toString());
|
||||
|
||||
builder.dismiss();
|
||||
|
||||
return Unit.INSTANCE;
|
||||
|
||||
});
|
||||
|
||||
builder.show();
|
||||
|
||||
}
|
||||
|
||||
private void onPreferenceChange(String newValue) {
|
||||
String selected = plugin.getSelected();
|
||||
plugin.getPluginsOptions().put(selected, new PluginOptions(selected, newValue));
|
||||
pluginOptsField.getValueTextView().setText(newValue);
|
||||
}
|
||||
|
||||
EditTextBoldCursor mkCursor() {
|
||||
|
||||
EditTextBoldCursor cursor = new EditTextBoldCursor(getParentActivity());
|
||||
cursor.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||
cursor.setHintColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText));
|
||||
cursor.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));
|
||||
cursor.setBackground(null);
|
||||
cursor.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));
|
||||
cursor.setCursorSize(AndroidUtilities.dp(20));
|
||||
cursor.setCursorWidth(1.5f);
|
||||
cursor.setSingleLine(true);
|
||||
cursor.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL);
|
||||
cursor.setHeaderHintColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueHeader));
|
||||
cursor.setTransformHintToHeader(true);
|
||||
cursor.setLineColors(Theme.getColor(Theme.key_windowBackgroundWhiteInputField), Theme.getColor(Theme.key_windowBackgroundWhiteInputFieldActivated), Theme.getColor(Theme.key_windowBackgroundWhiteRedText3));
|
||||
return cursor;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) {
|
||||
if (isOpen && !backward && currentProxyInfo == null) {
|
||||
ipField.requestFocus();
|
||||
AndroidUtilities.showKeyboard(ipField);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<ThemeDescription> getThemeDescriptions() {
|
||||
final ThemeDescription.ThemeDescriptionDelegate delegate = () -> {
|
||||
if (inputFields != null) {
|
||||
for (int i = 0; i < inputFields.length; i++) {
|
||||
inputFields[i].setLineColors(Theme.getColor(Theme.key_windowBackgroundWhiteInputField),
|
||||
Theme.getColor(Theme.key_windowBackgroundWhiteInputFieldActivated),
|
||||
Theme.getColor(Theme.key_windowBackgroundWhiteRedText3));
|
||||
}
|
||||
}
|
||||
};
|
||||
ArrayList<ThemeDescription> arrayList = new ArrayList<>();
|
||||
arrayList.add(new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault));
|
||||
arrayList.add(new ThemeDescription(scrollView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCH, null, null, null, null, Theme.key_actionBarDefaultSearch));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCHPLACEHOLDER, null, null, null, null, Theme.key_actionBarDefaultSearchPlaceholder));
|
||||
arrayList.add(new ThemeDescription(inputFieldsContainer, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite));
|
||||
arrayList.add(new ThemeDescription(linearLayout2, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider));
|
||||
|
||||
arrayList.add(new ThemeDescription(null, 0, null, null, null, null, delegate, Theme.key_windowBackgroundWhiteBlueText4));
|
||||
arrayList.add(new ThemeDescription(null, 0, null, null, null, null, delegate, Theme.key_windowBackgroundWhiteGrayText2));
|
||||
|
||||
|
||||
if (inputFields != null) {
|
||||
for (int a = 0; a < inputFields.length; a++) {
|
||||
arrayList.add(new ThemeDescription(inputFields[a], ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
|
||||
arrayList.add(new ThemeDescription(inputFields[a], ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText));
|
||||
arrayList.add(new ThemeDescription(inputFields[a], ThemeDescription.FLAG_HINTTEXTCOLOR | ThemeDescription.FLAG_PROGRESSBAR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader));
|
||||
arrayList.add(new ThemeDescription(inputFields[a], ThemeDescription.FLAG_CURSORCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
|
||||
arrayList.add(new ThemeDescription(null, 0, null, null, null, delegate, Theme.key_windowBackgroundWhiteInputField));
|
||||
arrayList.add(new ThemeDescription(null, 0, null, null, null, delegate, Theme.key_windowBackgroundWhiteInputFieldActivated));
|
||||
arrayList.add(new ThemeDescription(null, 0, null, null, null, delegate, Theme.key_windowBackgroundWhiteRedText3));
|
||||
}
|
||||
} else {
|
||||
arrayList.add(new ThemeDescription(null, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
|
||||
arrayList.add(new ThemeDescription(null, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText));
|
||||
}
|
||||
|
||||
arrayList.add(new ThemeDescription(bottomCell, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow));
|
||||
arrayList.add(new ThemeDescription(bottomCell, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4));
|
||||
arrayList.add(new ThemeDescription(bottomCell, ThemeDescription.FLAG_LINKCOLOR, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteLinkText));
|
||||
|
||||
return arrayList;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,348 @@
|
|||
/*
|
||||
* This is the source code of Telegram for Android v. 5.x.x.
|
||||
* It is licensed under GNU GPL v. 2 or later.
|
||||
* You should have received a copy of the license in this archive (see LICENSE).
|
||||
*
|
||||
* Copyright Nikolai Kudashov, 2013-2018.
|
||||
*/
|
||||
|
||||
package tw.nekomimi.nekogram;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.text.InputType;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
import org.telegram.messenger.AndroidUtilities;
|
||||
import org.telegram.messenger.LocaleController;
|
||||
import org.telegram.messenger.R;
|
||||
import org.telegram.messenger.SharedConfig;
|
||||
import org.telegram.ui.ActionBar.ActionBar;
|
||||
import org.telegram.ui.ActionBar.ActionBarMenu;
|
||||
import org.telegram.ui.ActionBar.AlertDialog;
|
||||
import org.telegram.ui.ActionBar.BaseFragment;
|
||||
import org.telegram.ui.ActionBar.Theme;
|
||||
import org.telegram.ui.ActionBar.ThemeDescription;
|
||||
import org.telegram.ui.Components.EditTextBoldCursor;
|
||||
import org.telegram.ui.Components.LayoutHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import kotlin.collections.ArraysKt;
|
||||
import kotlin.collections.CollectionsKt;
|
||||
import tw.nekomimi.nekogram.sub.SubInfo;
|
||||
import tw.nekomimi.nekogram.sub.SubManager;
|
||||
import tw.nekomimi.nekogram.utils.AlertUtil;
|
||||
import tw.nekomimi.nekogram.utils.UIUtil;
|
||||
|
||||
public class SubSettingsActivity extends BaseFragment {
|
||||
|
||||
private EditText[] inputFields;
|
||||
|
||||
private EditText urlsField;
|
||||
private EditTextBoldCursor remarksField;
|
||||
|
||||
private ScrollView scrollView;
|
||||
private LinearLayout linearLayout2;
|
||||
private LinearLayout inputFieldsContainer;
|
||||
|
||||
private SubInfo subInfo;
|
||||
|
||||
private boolean ignoreOnTextChange;
|
||||
|
||||
private static int done_button = 1;
|
||||
private static int menu_delete = 2;
|
||||
|
||||
public SubSettingsActivity() {
|
||||
super();
|
||||
subInfo = new SubInfo();
|
||||
subInfo.id = 0L;
|
||||
}
|
||||
|
||||
public SubSettingsActivity(SubInfo subInfo) {
|
||||
super();
|
||||
this.subInfo = subInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
AndroidUtilities.requestAdjustResize(getParentActivity(), classGuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View createView(Context context) {
|
||||
actionBar.setTitle(LocaleController.getString("ProxySubDetails", R.string.ProxySubDetails));
|
||||
actionBar.setBackButtonImage(R.drawable.ic_ab_back);
|
||||
actionBar.setAllowOverlayTitle(false);
|
||||
if (AndroidUtilities.isTablet()) {
|
||||
actionBar.setOccupyStatusBar(false);
|
||||
}
|
||||
|
||||
actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() {
|
||||
@Override
|
||||
public void onItemClick(int id) {
|
||||
if (id == -1) {
|
||||
finishFragment();
|
||||
} else if (id == done_button) {
|
||||
|
||||
if (getParentActivity() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(urlsField.getText())) {
|
||||
|
||||
urlsField.requestFocus();
|
||||
AndroidUtilities.showKeyboard(urlsField);
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(remarksField.getText())) {
|
||||
|
||||
remarksField.requestFocus();
|
||||
AndroidUtilities.showKeyboard(remarksField);
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
subInfo.urls = ArraysKt.toList(urlsField.getText().toString().split("\n"));
|
||||
subInfo.name = remarksField.getText().toString();
|
||||
|
||||
doGetProxies();
|
||||
|
||||
} else if (id == menu_delete) {
|
||||
|
||||
AlertUtil.showConfirm(getParentActivity(),
|
||||
LocaleController.getString("SubscriptionDelete", R.string.SubscriptionDelete),
|
||||
R.drawable.baseline_delete_24, LocaleController.getString("Delete", R.string.Delete),
|
||||
true, () -> {
|
||||
|
||||
AlertDialog pro = AlertUtil.showProgress(getParentActivity());
|
||||
|
||||
pro.show();
|
||||
|
||||
UIUtil.runOnIoDispatcher(() -> {
|
||||
|
||||
SubManager.getSubList().remove(subInfo);
|
||||
|
||||
SharedConfig.reloadProxyList();
|
||||
|
||||
UIUtil.runOnUIThread(() -> {
|
||||
|
||||
pro.dismiss();
|
||||
|
||||
finishFragment();
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ActionBarMenu menu = actionBar.createMenu();
|
||||
|
||||
if (subInfo.id != 0) {
|
||||
|
||||
menu.addItem(menu_delete, R.drawable.baseline_delete_24, AndroidUtilities.dp(56)).setContentDescription(LocaleController.getString("Delete", R.string.Delete));
|
||||
|
||||
}
|
||||
|
||||
menu.addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56)).setContentDescription(LocaleController.getString("Done", R.string.Done));
|
||||
|
||||
fragmentView = new FrameLayout(context);
|
||||
FrameLayout frameLayout = (FrameLayout) fragmentView;
|
||||
fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray));
|
||||
|
||||
scrollView = new ScrollView(context);
|
||||
scrollView.setFillViewport(true);
|
||||
AndroidUtilities.setScrollViewEdgeEffectColor(scrollView, Theme.getColor(Theme.key_actionBarDefault));
|
||||
frameLayout.addView(scrollView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT));
|
||||
|
||||
linearLayout2 = new LinearLayout(context);
|
||||
linearLayout2.setOrientation(LinearLayout.VERTICAL);
|
||||
scrollView.addView(linearLayout2, new ScrollView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
inputFieldsContainer = new LinearLayout(context);
|
||||
inputFieldsContainer.setOrientation(LinearLayout.VERTICAL);
|
||||
inputFieldsContainer.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite));
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
// bring to front for transitions
|
||||
inputFieldsContainer.setElevation(AndroidUtilities.dp(1f));
|
||||
inputFieldsContainer.setOutlineProvider(null);
|
||||
}
|
||||
linearLayout2.addView(inputFieldsContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
|
||||
|
||||
inputFields = new EditText[2];
|
||||
|
||||
urlsField = new EditText(context);
|
||||
inputFields[0] = urlsField;
|
||||
urlsField.setImeOptions(InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE);
|
||||
urlsField.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
|
||||
|
||||
urlsField.setHint(LocaleController.getString("SubscriptionUrls", R.string.SubscriptionUrls));
|
||||
urlsField.setText(CollectionsKt.joinToString(subInfo.urls, "\n", "", "", -1, "", null));
|
||||
urlsField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||
urlsField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText));
|
||||
urlsField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));
|
||||
urlsField.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP);
|
||||
urlsField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueHeader));
|
||||
urlsField.setSingleLine(false);
|
||||
urlsField.setMinLines(6);
|
||||
|
||||
inputFieldsContainer.addView(urlsField, LayoutHelper.createLinear(-1, -2, 17, 0, 17, 0));
|
||||
|
||||
FrameLayout container = new FrameLayout(context);
|
||||
|
||||
remarksField = mkCursor();
|
||||
inputFields[1] = remarksField;
|
||||
remarksField.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
remarksField.setHintText(LocaleController.getString("ProxyRemarks", R.string.ProxyRemarks));
|
||||
remarksField.setText(subInfo.name);
|
||||
remarksField.setSelection(remarksField.length());
|
||||
|
||||
container.addView(remarksField, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 17, 0, 17, 0));
|
||||
|
||||
inputFieldsContainer.addView((View) remarksField.getParent(), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
|
||||
return fragmentView;
|
||||
|
||||
}
|
||||
|
||||
EditTextBoldCursor mkCursor() {
|
||||
|
||||
EditTextBoldCursor cursor = new EditTextBoldCursor(getParentActivity());
|
||||
cursor.setSingleLine(true);
|
||||
cursor.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||
cursor.setHintColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText));
|
||||
cursor.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));
|
||||
cursor.setBackground(null);
|
||||
cursor.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));
|
||||
cursor.setCursorSize(AndroidUtilities.dp(20));
|
||||
cursor.setCursorWidth(1.5f);
|
||||
cursor.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL);
|
||||
cursor.setHeaderHintColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueHeader));
|
||||
cursor.setTransformHintToHeader(true);
|
||||
cursor.setLineColors(Theme.getColor(Theme.key_windowBackgroundWhiteInputField), Theme.getColor(Theme.key_windowBackgroundWhiteInputFieldActivated), Theme.getColor(Theme.key_windowBackgroundWhiteRedText3));
|
||||
return cursor;
|
||||
|
||||
}
|
||||
|
||||
public void doGetProxies() {
|
||||
|
||||
AlertDialog pro = AlertUtil.showProgress(getParentActivity(), LocaleController.getString("SubscriptionUpdating", R.string.SubscriptionUpdating));
|
||||
|
||||
AtomicBoolean canceled = new AtomicBoolean();
|
||||
|
||||
pro.setOnCancelListener((it) -> {
|
||||
|
||||
canceled.set(true);
|
||||
|
||||
});
|
||||
|
||||
pro.show();
|
||||
|
||||
UIUtil.runOnIoDispatcher(() -> {
|
||||
|
||||
try {
|
||||
|
||||
subInfo.proxies = subInfo.reloadProxies();
|
||||
subInfo.lastFetch = System.currentTimeMillis();
|
||||
|
||||
} catch (SubInfo.AllTriesFailed allTriesFailed) {
|
||||
|
||||
if (canceled.get()) return;
|
||||
|
||||
UIUtil.runOnUIThread(pro::dismiss);
|
||||
|
||||
AlertUtil.showSimpleAlert(getParentActivity(), "tries failed: " + allTriesFailed.toString().trim());
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if (subInfo.id == 0) subInfo.id = SubManager.getCount() + 10;
|
||||
|
||||
do {
|
||||
|
||||
try {
|
||||
SubManager.getSubList().update(subInfo, true);
|
||||
break;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
subInfo.id ++;
|
||||
|
||||
} while (true);
|
||||
|
||||
SharedConfig.reloadProxyList();
|
||||
|
||||
UIUtil.runOnUIThread(() -> {
|
||||
|
||||
pro.dismiss();
|
||||
|
||||
finishFragment();
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<ThemeDescription> getThemeDescriptions() {
|
||||
final ThemeDescription.ThemeDescriptionDelegate delegate = () -> {
|
||||
if (inputFields != null) {
|
||||
for (int i = 0; i < inputFields.length; i++) {
|
||||
inputFields[i].setText(Theme.getColor(Theme.key_windowBackgroundWhiteInputField));
|
||||
}
|
||||
}
|
||||
};
|
||||
ArrayList<ThemeDescription> arrayList = new ArrayList<>();
|
||||
arrayList.add(new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault));
|
||||
arrayList.add(new ThemeDescription(scrollView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCH, null, null, null, null, Theme.key_actionBarDefaultSearch));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCHPLACEHOLDER, null, null, null, null, Theme.key_actionBarDefaultSearchPlaceholder));
|
||||
arrayList.add(new ThemeDescription(inputFieldsContainer, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite));
|
||||
arrayList.add(new ThemeDescription(linearLayout2, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider));
|
||||
|
||||
arrayList.add(new ThemeDescription(null, 0, null, null, null, null, delegate, Theme.key_windowBackgroundWhiteBlueText4));
|
||||
arrayList.add(new ThemeDescription(null, 0, null, null, null, null, delegate, Theme.key_windowBackgroundWhiteGrayText2));
|
||||
|
||||
if (inputFields != null) {
|
||||
for (int a = 0; a < inputFields.length; a++) {
|
||||
arrayList.add(new ThemeDescription(inputFields[a], ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
|
||||
arrayList.add(new ThemeDescription(inputFields[a], ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText));
|
||||
arrayList.add(new ThemeDescription(inputFields[a], ThemeDescription.FLAG_HINTTEXTCOLOR | ThemeDescription.FLAG_PROGRESSBAR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader));
|
||||
arrayList.add(new ThemeDescription(inputFields[a], ThemeDescription.FLAG_CURSORCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
|
||||
arrayList.add(new ThemeDescription(null, 0, null, null, null, delegate, Theme.key_windowBackgroundWhiteInputField));
|
||||
arrayList.add(new ThemeDescription(null, 0, null, null, null, delegate, Theme.key_windowBackgroundWhiteInputFieldActivated));
|
||||
arrayList.add(new ThemeDescription(null, 0, null, null, null, delegate, Theme.key_windowBackgroundWhiteRedText3));
|
||||
}
|
||||
} else {
|
||||
arrayList.add(new ThemeDescription(null, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
|
||||
arrayList.add(new ThemeDescription(null, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText));
|
||||
}
|
||||
|
||||
return arrayList;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,450 @@
|
|||
package tw.nekomimi.nekogram
|
||||
|
||||
import cn.hutool.core.codec.Base64
|
||||
import com.google.gson.Gson
|
||||
import com.v2ray.ang.V2RayConfig
|
||||
import com.v2ray.ang.V2RayConfig.SOCKS_PROTOCOL
|
||||
import com.v2ray.ang.V2RayConfig.SS_PROTOCOL
|
||||
import com.v2ray.ang.V2RayConfig.VMESS1_PROTOCOL
|
||||
import com.v2ray.ang.V2RayConfig.VMESS_PROTOCOL
|
||||
import com.v2ray.ang.dto.AngConfig.VmessBean
|
||||
import com.v2ray.ang.dto.VmessQRCode
|
||||
import com.v2ray.ang.util.Utils
|
||||
import com.v2ray.ang.util.V2rayConfigUtil
|
||||
import libv2ray.Libv2ray
|
||||
import libv2ray.V2RayPoint
|
||||
import libv2ray.V2RayVPNServiceSupportsSet
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import org.telegram.messenger.FileLog
|
||||
import org.telegram.messenger.LocaleController
|
||||
import org.telegram.messenger.R
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.random.Random
|
||||
|
||||
class VmessLoader {
|
||||
|
||||
private val point = Libv2ray.newV2RayPoint(EmptyCallback(), true)
|
||||
|
||||
companion object {
|
||||
|
||||
fun parseVmess1Link(server: String): VmessBean {
|
||||
|
||||
val lnk = ("https://" + server.substringAfter(VMESS1_PROTOCOL)).toHttpUrl()
|
||||
|
||||
val bean = VmessBean()
|
||||
|
||||
bean.configType = V2RayConfig.EConfigType.Vmess
|
||||
bean.address = lnk.host
|
||||
bean.port = lnk.port
|
||||
bean.id = lnk.username
|
||||
bean.remarks = lnk.fragment ?: ""
|
||||
|
||||
lnk.queryParameterNames.forEach {
|
||||
|
||||
when (it) {
|
||||
|
||||
"tls" -> if (lnk.queryParameter(it) == "true") bean.streamSecurity = "tls"
|
||||
|
||||
"network" -> {
|
||||
|
||||
bean.network = lnk.queryParameter(it)!!
|
||||
|
||||
if (bean.network in arrayOf("http", "ws")) {
|
||||
|
||||
bean.path = Utils.urlDecode(lnk.encodedPath)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
"header" -> {
|
||||
|
||||
bean.headerType = lnk.queryParameter(it)!!
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return bean
|
||||
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun parseVmessLink(server: String): VmessBean {
|
||||
|
||||
try {
|
||||
if (server.isBlank()) error("empty link")
|
||||
|
||||
var vmess = VmessBean()
|
||||
|
||||
if (server.startsWith(VMESS_PROTOCOL)) {
|
||||
|
||||
val indexSplit = server.indexOf("?")
|
||||
if (indexSplit > 0) {
|
||||
vmess = resolveSimpleVmess1(server)
|
||||
} else {
|
||||
|
||||
var result = server.replace(VMESS_PROTOCOL, "")
|
||||
result = Base64.decodeStr(result)
|
||||
if (result.isBlank()) {
|
||||
error("invalid url format")
|
||||
}
|
||||
|
||||
if (result.contains("= vmess")) {
|
||||
|
||||
vmess = resolveSomeIOSAppShitCsvLink(result)
|
||||
|
||||
} else {
|
||||
|
||||
val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java)
|
||||
if (vmessQRCode.add.isBlank()
|
||||
|| vmessQRCode.port.isBlank()
|
||||
|| vmessQRCode.id.isBlank()
|
||||
|| vmessQRCode.aid.isBlank()
|
||||
|| vmessQRCode.net.isBlank()
|
||||
) {
|
||||
error("invalid protocol")
|
||||
}
|
||||
|
||||
vmess.configType = V2RayConfig.EConfigType.Vmess
|
||||
vmess.security = "auto"
|
||||
vmess.network = "tcp"
|
||||
vmess.headerType = "none"
|
||||
|
||||
vmess.configVersion = Utils.parseInt(vmessQRCode.v)
|
||||
vmess.remarks = vmessQRCode.ps
|
||||
vmess.address = vmessQRCode.add
|
||||
vmess.port = Utils.parseInt(vmessQRCode.port)
|
||||
vmess.id = vmessQRCode.id
|
||||
vmess.alterId = Utils.parseInt(vmessQRCode.aid)
|
||||
vmess.network = vmessQRCode.net
|
||||
vmess.headerType = vmessQRCode.type
|
||||
vmess.requestHost = vmessQRCode.host
|
||||
vmess.path = vmessQRCode.path
|
||||
vmess.streamSecurity = vmessQRCode.tls
|
||||
}
|
||||
}
|
||||
|
||||
upgradeServerVersion(vmess)
|
||||
|
||||
return vmess
|
||||
} else if (server.startsWith(VMESS1_PROTOCOL)) {
|
||||
|
||||
return parseVmess1Link(server)
|
||||
|
||||
} else if (server.startsWith(SS_PROTOCOL)) {
|
||||
var result = server.replace(SS_PROTOCOL, "")
|
||||
val indexSplit = result.indexOf("#")
|
||||
if (indexSplit > 0) {
|
||||
try {
|
||||
vmess.remarks = Utils.urlDecode(result.substring(indexSplit + 1, result.length))
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
result = result.substring(0, indexSplit)
|
||||
}
|
||||
|
||||
//part decode
|
||||
val indexS = result.indexOf("@")
|
||||
if (indexS > 0) {
|
||||
result = Base64.decodeStr(result.substring(0, indexS)) + result.substring(indexS, result.length)
|
||||
} else {
|
||||
result = Base64.decodeStr(result)
|
||||
}
|
||||
|
||||
val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)$".toRegex()
|
||||
val match = legacyPattern.matchEntire(result) ?: error("invalid protocol")
|
||||
vmess.security = match.groupValues[1].toLowerCase()
|
||||
vmess.id = match.groupValues[2]
|
||||
vmess.address = match.groupValues[3]
|
||||
if (vmess.address.firstOrNull() == '[' && vmess.address.lastOrNull() == ']')
|
||||
vmess.address = vmess.address.substring(1, vmess.address.length - 1)
|
||||
vmess.port = match.groupValues[4].toInt()
|
||||
|
||||
return vmess
|
||||
|
||||
} else if (server.startsWith(SOCKS_PROTOCOL)) {
|
||||
var result = server.replace(SOCKS_PROTOCOL, "")
|
||||
val indexSplit = result.indexOf("#")
|
||||
if (indexSplit > 0) {
|
||||
try {
|
||||
vmess.remarks = Utils.urlDecode(result.substring(indexSplit + 1, result.length))
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
result = result.substring(0, indexSplit)
|
||||
}
|
||||
|
||||
//part decode
|
||||
val indexS = result.indexOf(":")
|
||||
if (indexS < 0) {
|
||||
result = Base64.decodeStr(result)
|
||||
}
|
||||
|
||||
val legacyPattern = "^(.+?):(\\d+?)$".toRegex()
|
||||
val match = legacyPattern.matchEntire(result) ?: error("invalid protocol")
|
||||
vmess.address = match.groupValues[1]
|
||||
if (vmess.address.firstOrNull() == '[' && vmess.address.lastOrNull() == ']')
|
||||
vmess.address = vmess.address.substring(1, vmess.address.length - 1)
|
||||
vmess.port = match.groupValues[2].toInt()
|
||||
|
||||
return vmess
|
||||
} else {
|
||||
error("invalid protocol")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
||||
throw IllegalArgumentException(e)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun resolveSomeIOSAppShitCsvLink(csv: String): VmessBean {
|
||||
|
||||
val args = csv.split(",")
|
||||
|
||||
val bean = VmessBean()
|
||||
|
||||
bean.configType = V2RayConfig.EConfigType.Vmess
|
||||
bean.address = args[1]
|
||||
bean.port = args[2].toInt()
|
||||
bean.security = args[3]
|
||||
bean.id = args[4].replace("\"", "")
|
||||
|
||||
args.subList(5, args.size).forEach {
|
||||
|
||||
when {
|
||||
|
||||
it == "over-tls=true" -> {
|
||||
|
||||
bean.streamSecurity = "tls"
|
||||
|
||||
}
|
||||
|
||||
it.startsWith("tls-host=") -> {
|
||||
|
||||
bean.requestHost = it.substringAfter("=")
|
||||
|
||||
}
|
||||
|
||||
it.startsWith("obfs=") -> {
|
||||
|
||||
bean.network = it.substringAfter("=")
|
||||
|
||||
}
|
||||
|
||||
it.startsWith("obfs-path=") || it.contains("Host:") -> {
|
||||
|
||||
runCatching {
|
||||
|
||||
bean.path = it
|
||||
.substringAfter("obfs-path=\"")
|
||||
.substringBefore("\"obfs")
|
||||
|
||||
}
|
||||
|
||||
runCatching {
|
||||
|
||||
bean.requestHost = it
|
||||
.substringAfter("Host:")
|
||||
.substringBefore("[")
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return bean
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* upgrade
|
||||
*/
|
||||
private fun upgradeServerVersion(vmess: VmessBean): Int {
|
||||
try {
|
||||
if (vmess.configVersion == 2) {
|
||||
return 0
|
||||
}
|
||||
|
||||
when (vmess.network) {
|
||||
"kcp" -> {
|
||||
}
|
||||
"ws" -> {
|
||||
var path = ""
|
||||
var host = ""
|
||||
val lstParameter = vmess.requestHost.split(";")
|
||||
if (lstParameter.size > 0) {
|
||||
path = lstParameter.get(0).trim()
|
||||
}
|
||||
if (lstParameter.size > 1) {
|
||||
path = lstParameter.get(0).trim()
|
||||
host = lstParameter.get(1).trim()
|
||||
}
|
||||
vmess.path = path
|
||||
vmess.requestHost = host
|
||||
}
|
||||
"h2" -> {
|
||||
var path = ""
|
||||
var host = ""
|
||||
val lstParameter = vmess.requestHost.split(";")
|
||||
if (lstParameter.isNotEmpty()) {
|
||||
path = lstParameter[0].trim()
|
||||
}
|
||||
if (lstParameter.size > 1) {
|
||||
path = lstParameter[0].trim()
|
||||
host = lstParameter[1].trim()
|
||||
}
|
||||
vmess.path = path
|
||||
vmess.requestHost = host
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
vmess.configVersion = 2
|
||||
return 0
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveSimpleVmess1(server: String): VmessBean {
|
||||
|
||||
val vmess = VmessBean()
|
||||
|
||||
var result = server.replace(VMESS_PROTOCOL, "")
|
||||
val indexSplit = result.indexOf("?")
|
||||
if (indexSplit > 0) {
|
||||
result = result.substring(0, indexSplit)
|
||||
}
|
||||
result = Base64.decodeStr(result)
|
||||
|
||||
val arr1 = result.split('@')
|
||||
if (arr1.count() != 2) {
|
||||
return vmess
|
||||
}
|
||||
val arr21 = arr1[0].split(':')
|
||||
val arr22 = arr1[1].split(':')
|
||||
if (arr21.count() != 2 || arr21.count() != 2) {
|
||||
return vmess
|
||||
}
|
||||
|
||||
vmess.address = arr22[0]
|
||||
vmess.port = Utils.parseInt(arr22[1])
|
||||
vmess.security = arr21[0]
|
||||
vmess.id = arr21[1]
|
||||
|
||||
vmess.security = "chacha20-poly1305"
|
||||
vmess.network = "tcp"
|
||||
vmess.headerType = "none"
|
||||
vmess.remarks = ""
|
||||
vmess.alterId = 0
|
||||
|
||||
return vmess
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
lateinit var bean: VmessBean
|
||||
|
||||
fun initConfig(config: VmessBean) {
|
||||
|
||||
this.bean = config
|
||||
|
||||
}
|
||||
|
||||
fun start(): Int {
|
||||
|
||||
var retry = 3
|
||||
|
||||
do {
|
||||
|
||||
try {
|
||||
|
||||
val port = Random.nextInt(4096, 32768)
|
||||
|
||||
val conf = V2rayConfigUtil.getV2rayConfig(bean, port).content
|
||||
|
||||
runCatching {
|
||||
|
||||
Libv2ray.testConfig(conf)
|
||||
|
||||
}.onFailure {
|
||||
|
||||
FileLog.e(it)
|
||||
|
||||
return -1
|
||||
|
||||
}
|
||||
|
||||
point.configureFileContent = conf
|
||||
point.domainName = V2rayConfigUtil.currDomain
|
||||
|
||||
point.runLoop(true)
|
||||
|
||||
return port
|
||||
|
||||
} catch (e: Throwable) {
|
||||
|
||||
retry--
|
||||
|
||||
if (retry <= 0) {
|
||||
|
||||
FileLog.e(e)
|
||||
|
||||
return -1
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} while (true)
|
||||
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
|
||||
thread {
|
||||
|
||||
runCatching {
|
||||
|
||||
point.stopLoop()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class EmptyCallback : V2RayVPNServiceSupportsSet {
|
||||
override fun onEmitStatus(p0: Long, p1: String?): Long {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun setup(p0: String?): Long {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun prepare(): Long {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun shutdown(): Long {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun protect(p0: Long): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,505 @@
|
|||
/*
|
||||
* This is the source code of Telegram for Android v. 5.x.x.
|
||||
* It is licensed under GNU GPL v. 2 or later.
|
||||
* You should have received a copy of the license in this archive (see LICENSE).
|
||||
*
|
||||
* Copyright Nikolai Kudashov, 2013-2018.
|
||||
*/
|
||||
|
||||
package tw.nekomimi.nekogram;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.os.Build;
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.v2ray.ang.V2RayConfig;
|
||||
import com.v2ray.ang.dto.AngConfig;
|
||||
|
||||
import org.telegram.messenger.AndroidUtilities;
|
||||
import org.telegram.messenger.LocaleController;
|
||||
import org.telegram.messenger.R;
|
||||
import org.telegram.messenger.SharedConfig;
|
||||
import org.telegram.messenger.Utilities;
|
||||
import org.telegram.ui.ActionBar.ActionBar;
|
||||
import org.telegram.ui.ActionBar.ActionBarMenuItem;
|
||||
import org.telegram.ui.ActionBar.BaseFragment;
|
||||
import org.telegram.ui.ActionBar.Theme;
|
||||
import org.telegram.ui.ActionBar.ThemeDescription;
|
||||
import org.telegram.ui.Cells.TextCheckCell;
|
||||
import org.telegram.ui.Cells.TextInfoPrivacyCell;
|
||||
import org.telegram.ui.Cells.TextSettingsCell;
|
||||
import org.telegram.ui.Components.EditTextBoldCursor;
|
||||
import org.telegram.ui.Components.LayoutHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import kotlin.Unit;
|
||||
import tw.nekomimi.nekogram.utils.PopupBuilder;
|
||||
|
||||
public class VmessSettingsActivity extends BaseFragment {
|
||||
|
||||
private EditTextBoldCursor[] inputFields;
|
||||
|
||||
private EditTextBoldCursor ipField;
|
||||
private EditTextBoldCursor portField;
|
||||
private EditTextBoldCursor userIdField;
|
||||
private EditTextBoldCursor alterIdField;
|
||||
private TextSettingsCell securityField;
|
||||
private TextSettingsCell networkField;
|
||||
private TextSettingsCell headTypeField;
|
||||
private EditTextBoldCursor requestHostField;
|
||||
private EditTextBoldCursor pathField;
|
||||
private TextCheckCell useTlsField;
|
||||
private EditTextBoldCursor remarksField;
|
||||
|
||||
private ScrollView scrollView;
|
||||
private LinearLayout linearLayout2;
|
||||
private LinearLayout inputFieldsContainer;
|
||||
|
||||
private TextInfoPrivacyCell bottomCell;
|
||||
private ActionBarMenuItem doneItem;
|
||||
|
||||
private SharedConfig.VmessProxy currentProxyInfo;
|
||||
private AngConfig.VmessBean currentBean;
|
||||
|
||||
private boolean ignoreOnTextChange;
|
||||
|
||||
private static final int done_button = 1;
|
||||
|
||||
private static String[] securitySet = { "chacha20-poly1305","aes-128-gcm","auto","none" };
|
||||
private static String[] networkSet = { "tcp","kcp","ws","h2","quic" };
|
||||
private static String[] headTypeSet = { "none","http","srtp","utp","wechat-video","dtls","wireguard" };
|
||||
|
||||
public class TypeCell extends FrameLayout {
|
||||
|
||||
private TextView textView;
|
||||
private ImageView checkImage;
|
||||
private boolean needDivider;
|
||||
|
||||
public TypeCell(Context context) {
|
||||
super(context);
|
||||
|
||||
setWillNotDraw(false);
|
||||
|
||||
textView = new TextView(context);
|
||||
textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));
|
||||
textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||
textView.setLines(1);
|
||||
textView.setMaxLines(1);
|
||||
textView.setSingleLine(true);
|
||||
textView.setEllipsize(TextUtils.TruncateAt.END);
|
||||
textView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL);
|
||||
addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 23 + 48 : 21, 0, LocaleController.isRTL ? 21 : 23, 0));
|
||||
|
||||
checkImage = new ImageView(context);
|
||||
checkImage.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_featuredStickers_addedIcon), PorterDuff.Mode.SRC_IN));
|
||||
checkImage.setImageResource(R.drawable.sticker_added);
|
||||
addView(checkImage, LayoutHelper.createFrame(19, 14, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.CENTER_VERTICAL, 21, 0, 21, 0));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(50) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY));
|
||||
}
|
||||
|
||||
public void setValue(String name, boolean checked, boolean divider) {
|
||||
textView.setText(name);
|
||||
checkImage.setVisibility(checked ? VISIBLE : INVISIBLE);
|
||||
needDivider = divider;
|
||||
}
|
||||
|
||||
public void setTypeChecked(boolean value) {
|
||||
checkImage.setVisibility(value ? VISIBLE : INVISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
if (needDivider) {
|
||||
canvas.drawLine(LocaleController.isRTL ? 0 : AndroidUtilities.dp(20), getMeasuredHeight() - 1, getMeasuredWidth() - (LocaleController.isRTL ? AndroidUtilities.dp(20) : 0), getMeasuredHeight() - 1, Theme.dividerPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public VmessSettingsActivity() {
|
||||
super();
|
||||
currentBean = new AngConfig.VmessBean();
|
||||
currentBean.setConfigType(V2RayConfig.EConfigType.Vmess);
|
||||
}
|
||||
|
||||
public VmessSettingsActivity(SharedConfig.VmessProxy proxyInfo) {
|
||||
super();
|
||||
currentProxyInfo = proxyInfo;
|
||||
currentBean = proxyInfo.bean;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
AndroidUtilities.requestAdjustResize(getParentActivity(), classGuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View createView(Context context) {
|
||||
actionBar.setTitle(LocaleController.getString("ProxyDetails", R.string.ProxyDetails));
|
||||
actionBar.setBackButtonImage(R.drawable.ic_ab_back);
|
||||
actionBar.setAllowOverlayTitle(false);
|
||||
if (AndroidUtilities.isTablet()) {
|
||||
actionBar.setOccupyStatusBar(false);
|
||||
}
|
||||
|
||||
actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() {
|
||||
@Override
|
||||
public void onItemClick(int id) {
|
||||
if (id == -1) {
|
||||
finishFragment();
|
||||
} else if (id == done_button) {
|
||||
|
||||
if (getParentActivity() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(ipField.getText())) {
|
||||
|
||||
ipField.requestFocus();
|
||||
AndroidUtilities.showKeyboard(ipField);
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(portField.getText())) {
|
||||
|
||||
portField.requestFocus();
|
||||
AndroidUtilities.showKeyboard(portField);
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(userIdField.getText())) {
|
||||
|
||||
userIdField.requestFocus();
|
||||
AndroidUtilities.showKeyboard(userIdField);
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(alterIdField.getText())) {
|
||||
|
||||
alterIdField.requestFocus();
|
||||
AndroidUtilities.showKeyboard(alterIdField);
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
currentBean.setAddress(ipField.getText().toString());
|
||||
currentBean.setPort(Utilities.parseInt(portField.getText().toString()));
|
||||
currentBean.setId(userIdField.getText().toString());
|
||||
currentBean.setAlterId(Utilities.parseInt(alterIdField.getText().toString()));
|
||||
currentBean.setSecurity(securityField.getValueTextView().getText().toString());
|
||||
currentBean.setNetwork(networkField.getValueTextView().getText().toString());
|
||||
currentBean.setHeaderType(headTypeField.getValueTextView().getText().toString());
|
||||
currentBean.setRequestHost(requestHostField.getText().toString());
|
||||
currentBean.setPath(pathField.getText().toString());
|
||||
currentBean.setStreamSecurity(useTlsField.isChecked() ? "tls" : "");
|
||||
currentBean.setRemarks(remarksField.getText().toString());
|
||||
|
||||
if (currentProxyInfo == null) {
|
||||
currentProxyInfo = new SharedConfig.VmessProxy(currentBean);
|
||||
SharedConfig.addProxy(currentProxyInfo);
|
||||
SharedConfig.setCurrentProxy(currentProxyInfo);
|
||||
} else {
|
||||
currentProxyInfo.proxyCheckPingId = 0;
|
||||
currentProxyInfo.availableCheckTime = 0;
|
||||
currentProxyInfo.ping = 0;
|
||||
SharedConfig.saveProxyList();
|
||||
SharedConfig.setProxyEnable(false);
|
||||
}
|
||||
|
||||
finishFragment();
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
doneItem = actionBar.createMenu().addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56));
|
||||
doneItem.setContentDescription(LocaleController.getString("Done", R.string.Done));
|
||||
|
||||
fragmentView = new FrameLayout(context);
|
||||
FrameLayout frameLayout = (FrameLayout) fragmentView;
|
||||
fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray));
|
||||
|
||||
scrollView = new ScrollView(context);
|
||||
scrollView.setFillViewport(true);
|
||||
AndroidUtilities.setScrollViewEdgeEffectColor(scrollView, Theme.getColor(Theme.key_actionBarDefault));
|
||||
frameLayout.addView(scrollView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT));
|
||||
|
||||
linearLayout2 = new LinearLayout(context);
|
||||
linearLayout2.setOrientation(LinearLayout.VERTICAL);
|
||||
scrollView.addView(linearLayout2, new ScrollView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
inputFieldsContainer = new LinearLayout(context);
|
||||
inputFieldsContainer.setOrientation(LinearLayout.VERTICAL);
|
||||
inputFieldsContainer.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite));
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
// bring to front for transitions
|
||||
inputFieldsContainer.setElevation(AndroidUtilities.dp(1f));
|
||||
inputFieldsContainer.setOutlineProvider(null);
|
||||
}
|
||||
linearLayout2.addView(inputFieldsContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
|
||||
|
||||
inputFields = new EditTextBoldCursor[7];
|
||||
|
||||
for (int a = 0; a < 7; a++) {
|
||||
FrameLayout container = new FrameLayout(context);
|
||||
EditTextBoldCursor cursor = mkCursor();
|
||||
inputFields[a] = cursor;
|
||||
cursor.setImeOptions(EditorInfo.IME_ACTION_NEXT | EditorInfo.IME_FLAG_NO_EXTRACT_UI);
|
||||
switch (a) {
|
||||
case 0:
|
||||
ipField = cursor;
|
||||
cursor.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
cursor.setHintText(LocaleController.getString("UseProxyAddress", R.string.UseProxyAddress));
|
||||
cursor.setText(currentBean.getAddress());
|
||||
break;
|
||||
case 1:
|
||||
portField = cursor;
|
||||
cursor.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||
cursor.setHintText(LocaleController.getString("UseProxyPort", R.string.UseProxyPort));
|
||||
cursor.setText("" + currentBean.getPort());
|
||||
break;
|
||||
case 2:
|
||||
userIdField = cursor;
|
||||
cursor.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
cursor.setHintText(LocaleController.getString("VmessUserId", R.string.VmessUserId));
|
||||
cursor.setText(currentBean.getId());
|
||||
break;
|
||||
case 3:
|
||||
alterIdField = cursor;
|
||||
cursor.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||
cursor.setHintText(LocaleController.getString("VmessAlterId", R.string.VmessAlterId));
|
||||
cursor.setText("" + currentBean.getAlterId());
|
||||
break;
|
||||
case 4:
|
||||
requestHostField = cursor;
|
||||
cursor.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
cursor.setHintText(LocaleController.getString("VmessRequestHost", R.string.VmessRequestHost));
|
||||
cursor.setText(currentBean.getRequestHost());
|
||||
break;
|
||||
case 5:
|
||||
pathField = cursor;
|
||||
cursor.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
cursor.setHintText(LocaleController.getString("VmessPath", R.string.VmessPath));
|
||||
cursor.setText(currentBean.getPath());
|
||||
break;
|
||||
case 6:
|
||||
remarksField = cursor;
|
||||
cursor.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
cursor.setHintText(LocaleController.getString("ProxyRemarks", R.string.ProxyRemarks));
|
||||
cursor.setText(currentBean.getRemarks());
|
||||
break;
|
||||
}
|
||||
cursor.setSelection(cursor.length());
|
||||
|
||||
cursor.setPadding(0, 0, 0, 0);
|
||||
container.addView(cursor, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 17, a == 0 ? 12 : 0, 17, 0));
|
||||
|
||||
}
|
||||
|
||||
inputFieldsContainer.addView((View) ipField.getParent(), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
inputFieldsContainer.addView((View) portField.getParent(), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
inputFieldsContainer.addView((View) userIdField.getParent(), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
inputFieldsContainer.addView((View) alterIdField.getParent(), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
|
||||
FrameLayout container = new FrameLayout(context);
|
||||
|
||||
securityField = new TextSettingsCell(context);
|
||||
securityField.setBackground(Theme.getSelectorDrawable(false));
|
||||
securityField.setTextAndValue(LocaleController.getString("VmessSecurity", R.string.VmessSecurity), currentBean.getSecurity(), false);
|
||||
container.addView(securityField, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 0, 0, 0, 0));
|
||||
|
||||
securityField.setOnClickListener((v) -> {
|
||||
|
||||
PopupBuilder select = new PopupBuilder(v);
|
||||
|
||||
select.setItems(securitySet, (__,value) -> {
|
||||
|
||||
securityField.getValueTextView().setText(value);
|
||||
|
||||
return Unit.INSTANCE;
|
||||
|
||||
});
|
||||
|
||||
select.show();
|
||||
|
||||
});
|
||||
|
||||
inputFieldsContainer.addView(container, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
|
||||
container = new FrameLayout(context);
|
||||
networkField = new TextSettingsCell(context);
|
||||
networkField.setBackground(Theme.getSelectorDrawable(false));
|
||||
networkField.setTextAndValue(LocaleController.getString("VmessNetwork", R.string.VmessNetwork), currentBean.getNetwork(), false);
|
||||
container.addView(networkField, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 0, 0, 0, 0));
|
||||
|
||||
networkField.setOnClickListener((v) -> {
|
||||
|
||||
PopupBuilder select = new PopupBuilder(v);
|
||||
|
||||
select.setItems(networkSet, (__,value) -> {
|
||||
|
||||
networkField.getValueTextView().setText(value);
|
||||
|
||||
return Unit.INSTANCE;
|
||||
|
||||
});
|
||||
|
||||
select.show();
|
||||
|
||||
});
|
||||
|
||||
inputFieldsContainer.addView(container, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
|
||||
container = new FrameLayout(context);
|
||||
headTypeField = new TextSettingsCell(context);
|
||||
headTypeField.setBackground(Theme.getSelectorDrawable(false));
|
||||
headTypeField.setTextAndValue(LocaleController.getString("VmessHeadType", R.string.VmessHeadType), currentBean.getHeaderType(), false);
|
||||
container.addView(headTypeField, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 0, 0, 0, 0));
|
||||
|
||||
headTypeField.setOnClickListener((v) -> {
|
||||
|
||||
PopupBuilder select = new PopupBuilder(v);
|
||||
|
||||
select.setItems(headTypeSet, (__,value) -> {
|
||||
|
||||
headTypeField.getValueTextView().setText(value);
|
||||
|
||||
return Unit.INSTANCE;
|
||||
|
||||
});
|
||||
|
||||
select.show();
|
||||
|
||||
});
|
||||
|
||||
inputFieldsContainer.addView(container, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
|
||||
inputFieldsContainer.addView((View) requestHostField.getParent(), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
inputFieldsContainer.addView((View) pathField.getParent(), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
|
||||
container = new FrameLayout(context);
|
||||
useTlsField = new TextCheckCell(context);
|
||||
useTlsField.setBackground(Theme.getSelectorDrawable(false));
|
||||
useTlsField.setTextAndCheck(LocaleController.getString("VmessTls", R.string.VmessTls), !StrUtil.isBlank(currentBean.getStreamSecurity()), false);
|
||||
container.addView(useTlsField, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 0, 0, 0, 0));
|
||||
|
||||
useTlsField.setOnClickListener((v) -> useTlsField.setChecked(!useTlsField.isChecked()));
|
||||
|
||||
inputFieldsContainer.addView(container, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
|
||||
inputFieldsContainer.addView((View) remarksField.getParent(), LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
||||
|
||||
bottomCell = new TextInfoPrivacyCell(context);
|
||||
bottomCell.setBackground(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow));
|
||||
bottomCell.setText(LocaleController.getString("ProxyInfoVmess", R.string.ProxyInfoVmess));
|
||||
linearLayout2.addView(bottomCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
|
||||
|
||||
return fragmentView;
|
||||
|
||||
}
|
||||
|
||||
EditTextBoldCursor mkCursor() {
|
||||
|
||||
EditTextBoldCursor cursor = new EditTextBoldCursor(getParentActivity());
|
||||
cursor.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||
cursor.setHintColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText));
|
||||
cursor.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));
|
||||
cursor.setBackground(null);
|
||||
cursor.setCursorColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));
|
||||
cursor.setCursorSize(AndroidUtilities.dp(20));
|
||||
cursor.setCursorWidth(1.5f);
|
||||
cursor.setSingleLine(true);
|
||||
cursor.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL);
|
||||
cursor.setHeaderHintColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueHeader));
|
||||
cursor.setTransformHintToHeader(true);
|
||||
cursor.setLineColors(Theme.getColor(Theme.key_windowBackgroundWhiteInputField), Theme.getColor(Theme.key_windowBackgroundWhiteInputFieldActivated), Theme.getColor(Theme.key_windowBackgroundWhiteRedText3));
|
||||
return cursor;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) {
|
||||
if (isOpen && !backward && currentProxyInfo == null) {
|
||||
ipField.requestFocus();
|
||||
AndroidUtilities.showKeyboard(ipField);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<ThemeDescription> getThemeDescriptions() {
|
||||
final ThemeDescription.ThemeDescriptionDelegate delegate = () -> {
|
||||
if (inputFields != null) {
|
||||
for (int i = 0; i < inputFields.length; i++) {
|
||||
inputFields[i].setLineColors(Theme.getColor(Theme.key_windowBackgroundWhiteInputField),
|
||||
Theme.getColor(Theme.key_windowBackgroundWhiteInputFieldActivated),
|
||||
Theme.getColor(Theme.key_windowBackgroundWhiteRedText3));
|
||||
}
|
||||
}
|
||||
};
|
||||
ArrayList<ThemeDescription> arrayList = new ArrayList<>();
|
||||
arrayList.add(new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault));
|
||||
arrayList.add(new ThemeDescription(scrollView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCH, null, null, null, null, Theme.key_actionBarDefaultSearch));
|
||||
arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCHPLACEHOLDER, null, null, null, null, Theme.key_actionBarDefaultSearchPlaceholder));
|
||||
arrayList.add(new ThemeDescription(inputFieldsContainer, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite));
|
||||
arrayList.add(new ThemeDescription(linearLayout2, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider));
|
||||
|
||||
arrayList.add(new ThemeDescription(null, 0, null, null, null, null, delegate, Theme.key_windowBackgroundWhiteBlueText4));
|
||||
arrayList.add(new ThemeDescription(null, 0, null, null, null, null, delegate, Theme.key_windowBackgroundWhiteGrayText2));
|
||||
|
||||
|
||||
if (inputFields != null) {
|
||||
for (int a = 0; a < inputFields.length; a++) {
|
||||
arrayList.add(new ThemeDescription(inputFields[a], ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
|
||||
arrayList.add(new ThemeDescription(inputFields[a], ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText));
|
||||
arrayList.add(new ThemeDescription(inputFields[a], ThemeDescription.FLAG_HINTTEXTCOLOR | ThemeDescription.FLAG_PROGRESSBAR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader));
|
||||
arrayList.add(new ThemeDescription(inputFields[a], ThemeDescription.FLAG_CURSORCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
|
||||
arrayList.add(new ThemeDescription(null, 0, null, null, null, delegate, Theme.key_windowBackgroundWhiteInputField));
|
||||
arrayList.add(new ThemeDescription(null, 0, null, null, null, delegate, Theme.key_windowBackgroundWhiteInputFieldActivated));
|
||||
arrayList.add(new ThemeDescription(null, 0, null, null, null, delegate, Theme.key_windowBackgroundWhiteRedText3));
|
||||
}
|
||||
} else {
|
||||
arrayList.add(new ThemeDescription(null, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText));
|
||||
arrayList.add(new ThemeDescription(null, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText));
|
||||
}
|
||||
|
||||
arrayList.add(new ThemeDescription(bottomCell, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow));
|
||||
arrayList.add(new ThemeDescription(bottomCell, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4));
|
||||
arrayList.add(new ThemeDescription(bottomCell, ThemeDescription.FLAG_LINKCOLOR, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteLinkText));
|
||||
|
||||
return arrayList;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package tw.nekomimi.nekogram.parts
|
||||
|
||||
import android.os.SystemClock
|
||||
import cn.hutool.core.thread.ThreadUtil
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.telegram.messenger.AndroidUtilities
|
||||
import org.telegram.messenger.NotificationCenter
|
||||
import org.telegram.messenger.SharedConfig
|
||||
import org.telegram.messenger.SharedConfig.ExternalSocks5Proxy
|
||||
import org.telegram.ui.ProxyListActivity
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
@JvmOverloads
|
||||
fun ProxyListActivity.checkProxyList(force: Boolean, context: ExecutorService) {
|
||||
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
|
||||
SharedConfig.proxyList.toList().forEach {
|
||||
|
||||
if (it.checking || SystemClock.elapsedRealtime() - it.availableCheckTime < 2 * 60 * 1000L && !force) {
|
||||
|
||||
return@forEach
|
||||
|
||||
}
|
||||
|
||||
it.checking = true
|
||||
|
||||
runCatching {
|
||||
|
||||
context.execute {
|
||||
|
||||
runCatching {
|
||||
|
||||
val lock = AtomicBoolean()
|
||||
|
||||
checkSingleProxy(it, if (it is ExternalSocks5Proxy) 3 else 0) {
|
||||
|
||||
AndroidUtilities.runOnUIThread {
|
||||
|
||||
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.proxyCheckDone, it)
|
||||
|
||||
}
|
||||
|
||||
lock.set(true)
|
||||
|
||||
}
|
||||
|
||||
while (!lock.get()) ThreadUtil.sleep(100L)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}.onFailure {
|
||||
|
||||
return@launch
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
package tw.nekomimi.nekogram.sub;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.dizitart.no2.Document;
|
||||
import org.dizitart.no2.NitriteId;
|
||||
import org.dizitart.no2.mapper.Mappable;
|
||||
import org.dizitart.no2.mapper.NitriteMapper;
|
||||
import org.dizitart.no2.objects.Id;
|
||||
import org.dizitart.no2.objects.Index;
|
||||
import org.telegram.messenger.FileLog;
|
||||
import org.telegram.messenger.LocaleController;
|
||||
import org.telegram.messenger.R;
|
||||
import org.telegram.messenger.SharedConfig;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.xml.transform.ErrorListener;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import tw.nekomimi.nekogram.RelayBatonLoader;
|
||||
import tw.nekomimi.nekogram.utils.HttpUtil;
|
||||
import tw.nekomimi.nekogram.utils.ProxyUtil;
|
||||
|
||||
@Index("id")
|
||||
@SuppressWarnings("unchecked")
|
||||
public class SubInfo implements Mappable {
|
||||
|
||||
@Id
|
||||
public long id;
|
||||
public String name;
|
||||
public List<String> urls = new LinkedList<>();
|
||||
public List<String> proxies = new LinkedList<>();
|
||||
public Long lastFetch = -1L;
|
||||
public boolean enable = true;
|
||||
public boolean internal;
|
||||
|
||||
public String displayName() {
|
||||
|
||||
if (id == 1) return LocaleController.getString("PublicPrefix", R.string.PublicPrefix);
|
||||
|
||||
if (name.length() < 10) return name;
|
||||
|
||||
return name.substring(0,10) + "...";
|
||||
|
||||
}
|
||||
|
||||
public List<String> reloadProxies() throws AllTriesFailed {
|
||||
|
||||
HashMap<String,Exception> exceptions = new HashMap<>();
|
||||
|
||||
for (String url : urls) {
|
||||
|
||||
try {
|
||||
|
||||
String source = HttpUtil.get(url);
|
||||
|
||||
return ProxyUtil.parseProxies(source);
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
exceptions.put(url,e);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
throw new AllTriesFailed(exceptions);
|
||||
|
||||
}
|
||||
|
||||
public static class AllTriesFailed extends IOException {
|
||||
|
||||
public AllTriesFailed(HashMap<String,Exception> exceptions) {
|
||||
this.exceptions = exceptions;
|
||||
}
|
||||
|
||||
public HashMap<String,Exception> exceptions;
|
||||
|
||||
@NonNull @Override public String toString() {
|
||||
|
||||
StringBuilder errors = new StringBuilder();
|
||||
|
||||
for (Map.Entry<String, Exception> e : exceptions.entrySet()) {
|
||||
|
||||
errors.append(e.getKey()).append(": ");
|
||||
|
||||
errors.append(e.getValue().getClass().getSimpleName());
|
||||
|
||||
if (!StrUtil.isBlank(e.getValue().getMessage())) {
|
||||
|
||||
errors.append(" ( ");
|
||||
errors.append(e.getValue().getMessage());
|
||||
errors.append(" )");
|
||||
|
||||
}
|
||||
|
||||
errors.append("\n\n");
|
||||
|
||||
}
|
||||
|
||||
return errors.toString();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
SubInfo subInfo = (SubInfo) o;
|
||||
return id == subInfo.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document write(NitriteMapper mapper) {
|
||||
|
||||
Document document = new Document();
|
||||
|
||||
document.put("id", id);
|
||||
document.put("name", name);
|
||||
document.put("urls", urls);
|
||||
document.put("proxies",proxies);
|
||||
document.put("lastFetch", lastFetch);
|
||||
document.put("enable", enable);
|
||||
document.put("internal", internal);
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(NitriteMapper mapper, Document document) {
|
||||
|
||||
id = document.get("id", Long.class);
|
||||
name = document.get("name", String.class);
|
||||
urls = (List<String>) document.get("urls");
|
||||
proxies = (List<String>) document.get("proxies");
|
||||
|
||||
lastFetch = document.get("lastFetch",Long.class);
|
||||
enable = document.get("enable",Boolean.class);
|
||||
internal = document.get("internal",Boolean.class);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package tw.nekomimi.nekogram.sub
|
||||
|
||||
import org.dizitart.no2.objects.filters.ObjectFilters
|
||||
import org.telegram.messenger.LocaleController
|
||||
import org.telegram.messenger.R
|
||||
import tw.nekomimi.nekogram.database.mkDatabase
|
||||
|
||||
object SubManager {
|
||||
|
||||
val database by lazy { mkDatabase("proxy_sub") }
|
||||
|
||||
@JvmStatic val count get() = subList.find().totalCount()
|
||||
|
||||
@JvmStatic
|
||||
val subList by lazy {
|
||||
|
||||
database.getRepository("proxy_sub", SubInfo::class.java).apply {
|
||||
|
||||
val public = find(ObjectFilters.eq("id", 1L)).firstOrDefault()
|
||||
|
||||
update(SubInfo().apply {
|
||||
|
||||
name = LocaleController.getString("NekoXProxy", R.string.NekoXProxy)
|
||||
enable = public?.enable ?: true
|
||||
|
||||
urls = listOf(
|
||||
"https://gitlab.com/nekohasekai/nekox-proxy-list/-/raw/master/proxy_list",
|
||||
"https://nekox-dev.github.io/ProxyList/proxy_list",
|
||||
"https://gitee.com/nekoshizuku/AwesomeRepo/raw/master/proxy_list"
|
||||
)
|
||||
|
||||
id = 1L
|
||||
internal = true
|
||||
|
||||
proxies = public?.proxies ?: listOf()
|
||||
|
||||
}, true)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,482 @@
|
|||
package tw.nekomimi.nekogram.utils
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.util.Base64
|
||||
import android.view.Gravity
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Toast
|
||||
import com.google.zxing.*
|
||||
import com.google.zxing.common.GlobalHistogramBinarizer
|
||||
import com.google.zxing.qrcode.QRCodeReader
|
||||
import com.google.zxing.qrcode.QRCodeWriter
|
||||
import com.v2ray.ang.V2RayConfig.RB_PROTOCOL
|
||||
import com.v2ray.ang.V2RayConfig.SSR_PROTOCOL
|
||||
import com.v2ray.ang.V2RayConfig.SS_PROTOCOL
|
||||
import com.v2ray.ang.V2RayConfig.VMESS1_PROTOCOL
|
||||
import com.v2ray.ang.V2RayConfig.VMESS_PROTOCOL
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import org.telegram.messenger.*
|
||||
import org.telegram.messenger.browser.Browser
|
||||
import org.telegram.ui.ActionBar.BottomSheet
|
||||
import java.io.File
|
||||
import java.net.NetworkInterface
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
object ProxyUtil {
|
||||
|
||||
@JvmStatic
|
||||
fun isVPNEnabled(): Boolean {
|
||||
|
||||
val networkList = mutableListOf<String>()
|
||||
|
||||
runCatching {
|
||||
|
||||
Collections.list(NetworkInterface.getNetworkInterfaces()).forEach {
|
||||
|
||||
if (it.isUp) networkList.add(it.name)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return networkList.contains("tun0")
|
||||
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun parseProxies(_text: String): MutableList<String> {
|
||||
|
||||
val text = runCatching {
|
||||
|
||||
String(Base64.decode(_text, Base64.NO_PADDING))
|
||||
|
||||
}.recover {
|
||||
|
||||
_text
|
||||
|
||||
}.getOrThrow()
|
||||
|
||||
val proxies = mutableListOf<String>()
|
||||
|
||||
text.split('\n').map { it.split(" ") }.forEach {
|
||||
|
||||
it.forEach { line ->
|
||||
|
||||
if (line.startsWith("tg://proxy") ||
|
||||
line.startsWith("tg://socks") ||
|
||||
line.startsWith("https://t.me/proxy") ||
|
||||
line.startsWith("https://t.me/socks") ||
|
||||
line.startsWith(VMESS_PROTOCOL) ||
|
||||
line.startsWith(VMESS1_PROTOCOL) ||
|
||||
line.startsWith(SS_PROTOCOL) ||
|
||||
line.startsWith(SSR_PROTOCOL) /*||
|
||||
line.startsWith(RB_PROTOCOL)*/) {
|
||||
|
||||
runCatching { proxies.add(SharedConfig.parseProxyInfo(line).toUrl()) }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (proxies.isEmpty()) error("no proxy link found")
|
||||
|
||||
return proxies
|
||||
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun importFromClipboard() {
|
||||
|
||||
var text = (ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).primaryClip?.getItemAt(0)?.text?.toString()
|
||||
|
||||
if (text != null) {
|
||||
|
||||
runCatching {
|
||||
|
||||
text = String(Base64.decode(text, Base64.NO_PADDING))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val proxies = mutableListOf<SharedConfig.ProxyInfo>()
|
||||
|
||||
var error = false
|
||||
|
||||
text?.split('\n')?.map { it.split(" ") }?.forEach {
|
||||
|
||||
it.forEach { line ->
|
||||
|
||||
if (line.startsWith("tg://proxy") ||
|
||||
line.startsWith("tg://socks") ||
|
||||
line.startsWith("https://t.me/proxy") ||
|
||||
line.startsWith("https://t.me/socks") ||
|
||||
line.startsWith(VMESS_PROTOCOL) ||
|
||||
line.startsWith(VMESS1_PROTOCOL) ||
|
||||
line.startsWith(SS_PROTOCOL) ||
|
||||
line.startsWith(SSR_PROTOCOL) /*||
|
||||
line.startsWith(RB_PROTOCOL)*/) {
|
||||
|
||||
runCatching { proxies.add(SharedConfig.parseProxyInfo(line)) }.onFailure {
|
||||
|
||||
AlertUtil.showToast(LocaleController.getString("BrokenLink", R.string.BrokenLink) + ": ${it.message ?: it.javaClass.simpleName}")
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (proxies.isNullOrEmpty()) {
|
||||
|
||||
if (!error) AlertUtil.showToast(LocaleController.getString("BrokenLink", R.string.BrokenLink))
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
proxies.forEach {
|
||||
|
||||
SharedConfig.addProxy(it)
|
||||
|
||||
}
|
||||
|
||||
UIUtil.runOnUIThread {
|
||||
|
||||
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.proxySettingsChanged)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun importProxy(ctx: Context, link: String): Boolean {
|
||||
|
||||
runCatching {
|
||||
|
||||
if (link.startsWith(VMESS_PROTOCOL) || link.startsWith(VMESS1_PROTOCOL)) {
|
||||
|
||||
AndroidUtilities.showVmessAlert(ctx, SharedConfig.VmessProxy(link))
|
||||
|
||||
} else if (link.startsWith(SS_PROTOCOL)) {
|
||||
|
||||
AndroidUtilities.showShadowsocksAlert(ctx, SharedConfig.ShadowsocksProxy(link))
|
||||
|
||||
} else if (link.startsWith(SSR_PROTOCOL)) {
|
||||
|
||||
AndroidUtilities.showShadowsocksRAlert(ctx, SharedConfig.ShadowsocksRProxy(link))
|
||||
|
||||
} else if (link.startsWith(RB_PROTOCOL)) {
|
||||
|
||||
AndroidUtilities.showRelayBatonAlert(ctx, SharedConfig.RelayBatonProxy(link))
|
||||
|
||||
} else {
|
||||
|
||||
val url = link.toHttpUrlOrNull()!!
|
||||
|
||||
AndroidUtilities.showProxyAlert(ctx,
|
||||
url.queryParameter("server"),
|
||||
url.queryParameter("port"),
|
||||
url.queryParameter("user"),
|
||||
url.queryParameter("pass"),
|
||||
url.queryParameter("secret"),
|
||||
url.fragment)
|
||||
|
||||
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
}.onFailure {
|
||||
|
||||
FileLog.w("$it")
|
||||
|
||||
AlertUtil.showToast("${LocaleController.getString("BrokenLink", R.string.BrokenLink)}: ${it.message}")
|
||||
|
||||
}
|
||||
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun importInBackground(link: String): SharedConfig.ProxyInfo {
|
||||
|
||||
val info = runCatching {
|
||||
|
||||
if (link.startsWith(VMESS_PROTOCOL) || link.startsWith(VMESS1_PROTOCOL)) {
|
||||
|
||||
SharedConfig.VmessProxy(link)
|
||||
|
||||
} else if (link.startsWith(SS_PROTOCOL)) {
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
|
||||
error(LocaleController.getString("MinApi21Required", R.string.MinApi21Required))
|
||||
|
||||
}
|
||||
|
||||
SharedConfig.ShadowsocksProxy(link)
|
||||
|
||||
} else if (link.startsWith(SSR_PROTOCOL)) {
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
|
||||
error(LocaleController.getString("MinApi21Required", R.string.MinApi21Required))
|
||||
|
||||
}
|
||||
|
||||
SharedConfig.ShadowsocksRProxy(link)
|
||||
|
||||
} else {
|
||||
|
||||
SharedConfig.ProxyInfo.fromUrl(link)
|
||||
|
||||
}
|
||||
|
||||
}.getOrThrow()
|
||||
|
||||
if (!(SharedConfig.addProxy(info) === info)) {
|
||||
|
||||
error("already exists")
|
||||
|
||||
}
|
||||
|
||||
return info
|
||||
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun shareProxy(ctx: Activity, info: SharedConfig.ProxyInfo, type: Int) {
|
||||
|
||||
val url = info.toUrl();
|
||||
|
||||
if (type == 1) {
|
||||
|
||||
AndroidUtilities.addToClipboard(url)
|
||||
|
||||
Toast.makeText(ctx, LocaleController.getString("LinkCopied", R.string.LinkCopied), Toast.LENGTH_LONG).show()
|
||||
|
||||
} else if (type == 0) {
|
||||
|
||||
val shareIntent = Intent(Intent.ACTION_SEND)
|
||||
|
||||
shareIntent.type = "text/plain"
|
||||
|
||||
shareIntent.putExtra(Intent.EXTRA_TEXT, url)
|
||||
|
||||
val chooserIntent = Intent.createChooser(shareIntent, LocaleController.getString("ShareLink", R.string.ShareLink))
|
||||
|
||||
chooserIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
|
||||
ctx.startActivity(chooserIntent)
|
||||
|
||||
} else {
|
||||
|
||||
showQrDialog(ctx, url)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getOwnerActivity(ctx: Context): Activity {
|
||||
|
||||
if (ctx is Activity) return ctx
|
||||
|
||||
if (ctx is ContextWrapper) return getOwnerActivity(ctx.baseContext)
|
||||
|
||||
error("unable cast ${ctx.javaClass.name} to activity")
|
||||
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun showQrDialog(ctx: Context, text: String) {
|
||||
|
||||
val code = createQRCode(text)
|
||||
|
||||
ctx.setTheme(R.style.Theme_TMessages)
|
||||
|
||||
android.app.AlertDialog.Builder(ctx).setView(LinearLayout(ctx).apply {
|
||||
|
||||
addView(LinearLayout(ctx).apply {
|
||||
|
||||
gravity = Gravity.CENTER
|
||||
|
||||
val width = AndroidUtilities.dp(330f)
|
||||
|
||||
addView(ImageView(ctx).apply {
|
||||
|
||||
setImageBitmap(code)
|
||||
|
||||
scaleType = ImageView.ScaleType.FIT_XY
|
||||
|
||||
setOnLongClickListener {
|
||||
|
||||
BottomSheet.Builder(ctx).setItems(arrayOf(
|
||||
|
||||
LocaleController.getString("SaveToGallery", R.string.SaveToGallery),
|
||||
LocaleController.getString("Cancel", R.string.Cancel)
|
||||
|
||||
), intArrayOf(
|
||||
|
||||
R.drawable.baseline_image_24,
|
||||
R.drawable.baseline_cancel_24
|
||||
|
||||
)) { _, i ->
|
||||
|
||||
if (i == 0) {
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 23 && ctx.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||
|
||||
getOwnerActivity(ctx).requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 4)
|
||||
|
||||
return@setItems
|
||||
|
||||
}
|
||||
|
||||
val saveTo = File(Environment.getExternalStorageDirectory(), "${Environment.DIRECTORY_PICTURES}/share_${text.hashCode()}.jpg")
|
||||
|
||||
saveTo.parentFile?.mkdirs()
|
||||
|
||||
runCatching {
|
||||
|
||||
saveTo.createNewFile()
|
||||
|
||||
saveTo.outputStream().use {
|
||||
|
||||
code?.compress(Bitmap.CompressFormat.JPEG, 100, it);
|
||||
|
||||
}
|
||||
|
||||
AndroidUtilities.addMediaToGallery(saveTo.path)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}.show()
|
||||
|
||||
return@setOnLongClickListener true
|
||||
|
||||
}
|
||||
|
||||
}, LinearLayout.LayoutParams(width, width))
|
||||
|
||||
}, LinearLayout.LayoutParams(-1, -1).apply {
|
||||
|
||||
gravity = Gravity.CENTER
|
||||
|
||||
})
|
||||
|
||||
}).show()
|
||||
|
||||
}
|
||||
|
||||
fun createQRCode(text: String, size: Int = 800): Bitmap? {
|
||||
try {
|
||||
val hints = HashMap<EncodeHintType, Any>()
|
||||
hints[EncodeHintType.CHARACTER_SET] = "utf-8"
|
||||
//hints[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.H
|
||||
val bitMatrix = QRCodeWriter().encode(text, BarcodeFormat.QR_CODE, size, size, hints)
|
||||
val pixels = IntArray(size * size)
|
||||
for (y in 0 until size) {
|
||||
for (x in 0 until size) {
|
||||
if (bitMatrix.get(x, y)) {
|
||||
pixels[y * size + x] = 0xff000000.toInt()
|
||||
} else {
|
||||
pixels[y * size + x] = 0xffffffff.toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
|
||||
bitmap.setPixels(pixels, 0, size, 0, 0, size, size)
|
||||
return bitmap
|
||||
} catch (e: WriterException) {
|
||||
e.printStackTrace()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
val qrReader = QRCodeReader()
|
||||
|
||||
@JvmStatic
|
||||
fun tryReadQR(ctx: Activity, bitmap: Bitmap) {
|
||||
|
||||
val intArray = IntArray(bitmap.getWidth() * bitmap.getHeight())
|
||||
bitmap.getPixels(intArray, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight())
|
||||
val source = RGBLuminanceSource(bitmap.getWidth(), bitmap.getHeight(), intArray)
|
||||
|
||||
try {
|
||||
|
||||
val result = qrReader.decode(BinaryBitmap(GlobalHistogramBinarizer(source)))
|
||||
|
||||
if (result == null || result.text.isBlank()) {
|
||||
|
||||
AlertUtil.showToast(LocaleController.getString("NoQrFound", R.string.NoQrFound))
|
||||
|
||||
} else {
|
||||
|
||||
showLinkAlert(ctx, result.text)
|
||||
|
||||
}
|
||||
|
||||
} catch (ex: NoSuchMethodError) {
|
||||
|
||||
AlertUtil.showSimpleAlert(ctx, "很抱歉, 這是一個已知的問題, 但您現在無法掃碼, 因爲您正在使用糟糕的Android系統, 直到 Google Zxing 為您的設備做出優化.")
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun showLinkAlert(ctx: Activity, text: String) {
|
||||
|
||||
val builder = BottomSheet.Builder(ctx)
|
||||
|
||||
var isUrl = false
|
||||
|
||||
runCatching {
|
||||
text.toHttpUrlOrNull()!!
|
||||
if (Browser.isInternalUrl(text, booleanArrayOf(false))) {
|
||||
Browser.openUrl(ctx, text)
|
||||
return
|
||||
}
|
||||
isUrl = true
|
||||
}
|
||||
|
||||
builder.setTitle(text)
|
||||
|
||||
builder.setItems(arrayOf(
|
||||
if (isUrl) LocaleController.getString("Open", R.string.OpenUrlTitle) else null,
|
||||
LocaleController.getString("Copy", R.string.Copy),
|
||||
LocaleController.getString("Cancel", R.string.Cancel)
|
||||
)) { _, i ->
|
||||
if (i == 0) {
|
||||
Browser.openUrl(ctx, text)
|
||||
} else if (i == 1) {
|
||||
AndroidUtilities.addToClipboard(text)
|
||||
Toast.makeText(ApplicationLoader.applicationContext, LocaleController.getString("LinkCopied", R.string.LinkCopied), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
builder.show()
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue