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 String DOMAIN_NAME_STR = "(" + HOST_NAME + "|" + IP_ADDRESS_STRING + ")";
|
||||||
private static final Pattern DOMAIN_NAME = Pattern.compile(DOMAIN_NAME_STR);
|
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 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 WORD_BOUNDARY = "(?:\\b|$|^)";
|
||||||
private static final String USER_INFO = "(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
|
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,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
|
||||||
+ "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@";
|
+ "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@";
|
||||||
private static final String PORT_NUMBER = "\\:\\d{1,5}";
|
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 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 RELAXED_DOMAIN_NAME = "(?:" + "(?:" + IRI_LABEL + "(?:\\.(?=\\S))" + "?)+" + "|" + IP_ADDRESS_STRING + ")";
|
||||||
|
|
||||||
private static final String WEB_URL_WITHOUT_PROTOCOL = "("
|
private static final String WEB_URL_WITHOUT_PROTOCOL = "("
|
||||||
|
@ -185,7 +188,20 @@ public class LinkifyPort {
|
||||||
+ "(?:" + PATH_AND_QUERY + ")?"
|
+ "(?:" + PATH_AND_QUERY + ")?"
|
||||||
+ WORD_BOUNDARY
|
+ 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 WEB_URL = null;
|
||||||
|
public static Pattern PROXY_PATTERN = Pattern.compile(PROXY_URL);
|
||||||
|
|
||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -348,7 +348,7 @@ public class Browser {
|
||||||
|
|
||||||
}
|
}
|
||||||
return true;
|
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;
|
return true;
|
||||||
} else if ("telegram.dog".equals(host)) {
|
} else if ("telegram.dog".equals(host)) {
|
||||||
String path = uri.getPath();
|
String path = uri.getPath();
|
||||||
|
|
|
@ -64,7 +64,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.LinearSmoothScroller;
|
import androidx.recyclerview.widget.LinearSmoothScroller;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import tw.nekomimi.nekogram.NekoConfig;
|
import tw.nekomimi.nekogram.utils.EnvUtil;
|
||||||
|
|
||||||
public class ChatAttachAlertDocumentLayout extends ChatAttachAlert.AttachAlertLayout {
|
public class ChatAttachAlertDocumentLayout extends ChatAttachAlert.AttachAlertLayout {
|
||||||
|
|
||||||
|
@ -109,6 +109,12 @@ public class ChatAttachAlertDocumentLayout extends ChatAttachAlert.AttachAlertLa
|
||||||
private boolean canSelectOnlyImageFiles;
|
private boolean canSelectOnlyImageFiles;
|
||||||
private boolean allowMusic;
|
private boolean allowMusic;
|
||||||
|
|
||||||
|
public void setAllowPhoto(boolean allowPhoto) {
|
||||||
|
this.allowPhoto = allowPhoto;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean allowPhoto = true;
|
||||||
|
|
||||||
private boolean searching;
|
private boolean searching;
|
||||||
|
|
||||||
private boolean sortByName;
|
private boolean sortByName;
|
||||||
|
@ -222,7 +228,7 @@ public class ChatAttachAlertDocumentLayout extends ChatAttachAlert.AttachAlertLa
|
||||||
|
|
||||||
emptyImageView = new ImageView(context);
|
emptyImageView = new ImageView(context);
|
||||||
emptyImageView.setImageResource(R.drawable.files_empty);
|
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));
|
emptyView.addView(emptyImageView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT));
|
||||||
|
|
||||||
emptyTitleTextView = new TextView(context);
|
emptyTitleTextView = new TextView(context);
|
||||||
|
@ -969,12 +975,7 @@ public class ChatAttachAlertDocumentLayout extends ChatAttachAlert.AttachAlertLa
|
||||||
|
|
||||||
ListItem fs;
|
ListItem fs;
|
||||||
try {
|
try {
|
||||||
File telegramPath;
|
File telegramPath = EnvUtil.getTelegramPath();
|
||||||
if (NekoConfig.saveCacheToPrivateDirectory) {
|
|
||||||
telegramPath = new File(ApplicationLoader.applicationContext.getFilesDir(), "Telegram");
|
|
||||||
} else {
|
|
||||||
telegramPath = new File(Environment.getExternalStorageDirectory(), "Telegram");
|
|
||||||
}
|
|
||||||
if (telegramPath.exists()) {
|
if (telegramPath.exists()) {
|
||||||
fs = new ListItem();
|
fs = new ListItem();
|
||||||
fs.title = "Telegram";
|
fs.title = "Telegram";
|
||||||
|
@ -987,12 +988,16 @@ public class ChatAttachAlertDocumentLayout extends ChatAttachAlert.AttachAlertLa
|
||||||
FileLog.e(e);
|
FileLog.e(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
fs = new ListItem();
|
if (allowPhoto) {
|
||||||
fs.title = LocaleController.getString("Gallery", R.string.Gallery);
|
|
||||||
fs.subtitle = LocaleController.getString("GalleryInfo", R.string.GalleryInfo);
|
fs = new ListItem();
|
||||||
fs.icon = R.drawable.files_gallery;
|
fs.title = LocaleController.getString("Gallery", R.string.Gallery);
|
||||||
fs.file = null;
|
fs.subtitle = LocaleController.getString("GalleryInfo", R.string.GalleryInfo);
|
||||||
items.add(fs);
|
fs.icon = R.drawable.files_gallery;
|
||||||
|
fs.file = null;
|
||||||
|
items.add(fs);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
if (allowMusic) {
|
if (allowMusic) {
|
||||||
fs = new ListItem();
|
fs = new ListItem();
|
||||||
|
|
|
@ -26,6 +26,8 @@ public class PickerBottomLayout extends FrameLayout {
|
||||||
|
|
||||||
public LinearLayout doneButton;
|
public LinearLayout doneButton;
|
||||||
public TextView cancelButton;
|
public TextView cancelButton;
|
||||||
|
public LinearLayout middleButton;
|
||||||
|
public TextView middleButtonTextView;
|
||||||
public TextView doneButtonTextView;
|
public TextView doneButtonTextView;
|
||||||
public TextView doneButtonBadgeTextView;
|
public TextView doneButtonBadgeTextView;
|
||||||
|
|
||||||
|
@ -48,11 +50,31 @@ public class PickerBottomLayout extends FrameLayout {
|
||||||
cancelButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
|
cancelButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
|
||||||
addView(cancelButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT));
|
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 = new LinearLayout(context);
|
||||||
doneButton.setOrientation(LinearLayout.HORIZONTAL);
|
doneButton.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
doneButton.setBackgroundDrawable(Theme.createSelectorDrawable(0x0f000000, 0));
|
doneButton.setBackgroundDrawable(Theme.createSelectorDrawable(0x0f000000, 0));
|
||||||
doneButton.setPadding(AndroidUtilities.dp(33), 0, AndroidUtilities.dp(33), 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 = new TextView(context);
|
||||||
doneButtonBadgeTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
|
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.ExoPlayer;
|
||||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
||||||
|
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.json.JSONTokener;
|
import org.json.JSONTokener;
|
||||||
|
@ -59,25 +61,19 @@ import org.telegram.messenger.ImageReceiver;
|
||||||
import org.telegram.messenger.R;
|
import org.telegram.messenger.R;
|
||||||
import org.telegram.messenger.Utilities;
|
import org.telegram.messenger.Utilities;
|
||||||
import org.telegram.tgnet.TLRPC;
|
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.URLDecoder;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.zip.GZIPInputStream;
|
|
||||||
|
|
||||||
public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerDelegate, AudioManager.OnAudioFocusChangeListener {
|
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) {
|
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) {
|
|
||||||
|
|
||||||
}
|
OkHttpClient client = HttpUtil.getOkHttpClientWithCurrProxy().newBuilder()
|
||||||
httpConnection = downloadUrl.openConnection();
|
.followRedirects(true)
|
||||||
httpConnection.connect();
|
.followSslRedirects(true)
|
||||||
httpConnectionStream = httpConnection.getInputStream();
|
.build();
|
||||||
}
|
|
||||||
} else {
|
Request.Builder request = new Request.Builder().url(url)
|
||||||
httpConnectionStream = httpConnection.getInputStream();
|
.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 {
|
try {
|
||||||
if (httpConnection instanceof HttpURLConnection) {
|
return client.newCall(request.build()).execute().body().string();
|
||||||
int code = ((HttpURLConnection) httpConnection).getResponseCode();
|
|
||||||
if (code != HttpURLConnection.HTTP_OK && code != HttpURLConnection.HTTP_ACCEPTED && code != HttpURLConnection.HTTP_NOT_MODIFIED) {
|
|
||||||
//canRetry = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
FileLog.e(e);
|
FileLog.e(e);
|
||||||
}
|
}
|
||||||
|
count ++;
|
||||||
|
ThreadUtil.sleep(1000);
|
||||||
|
} while (count < 3);
|
||||||
|
|
||||||
if (httpConnectionStream != null) {
|
return 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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[]> {
|
private class YoutubeVideoTask extends AsyncTask<Void, Void, String[]> {
|
||||||
|
|
|
@ -15,12 +15,11 @@ import android.app.Activity;
|
||||||
import android.app.ActivityManager;
|
import android.app.ActivityManager;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageInfo;
|
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
import android.graphics.Shader;
|
import android.graphics.Shader;
|
||||||
import android.graphics.drawable.BitmapDrawable;
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
|
@ -50,43 +49,50 @@ import android.widget.LinearLayout;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.Toast;
|
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.AccountInstance;
|
||||||
import org.telegram.messenger.AndroidUtilities;
|
import org.telegram.messenger.AndroidUtilities;
|
||||||
|
import org.telegram.messenger.ApplicationLoader;
|
||||||
import org.telegram.messenger.BuildVars;
|
import org.telegram.messenger.BuildVars;
|
||||||
import org.telegram.messenger.ChatObject;
|
import org.telegram.messenger.ChatObject;
|
||||||
import org.telegram.messenger.ContactsController;
|
import org.telegram.messenger.ContactsController;
|
||||||
import org.telegram.messenger.LocationController;
|
|
||||||
import org.telegram.messenger.MediaDataController;
|
|
||||||
import org.telegram.messenger.FileLoader;
|
import org.telegram.messenger.FileLoader;
|
||||||
|
import org.telegram.messenger.FileLog;
|
||||||
import org.telegram.messenger.ImageLoader;
|
import org.telegram.messenger.ImageLoader;
|
||||||
|
import org.telegram.messenger.LocaleController;
|
||||||
import org.telegram.messenger.MediaController;
|
import org.telegram.messenger.MediaController;
|
||||||
|
import org.telegram.messenger.MediaDataController;
|
||||||
import org.telegram.messenger.MessageObject;
|
import org.telegram.messenger.MessageObject;
|
||||||
import org.telegram.messenger.MessagesController;
|
import org.telegram.messenger.MessagesController;
|
||||||
import org.telegram.messenger.MessagesStorage;
|
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.NotificationCenter;
|
||||||
import org.telegram.messenger.R;
|
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.browser.Browser;
|
||||||
import org.telegram.messenger.camera.CameraController;
|
import org.telegram.messenger.camera.CameraController;
|
||||||
import org.telegram.tgnet.ConnectionsManager;
|
import org.telegram.tgnet.ConnectionsManager;
|
||||||
import org.telegram.tgnet.TLRPC;
|
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.ActionBarLayout;
|
||||||
|
import org.telegram.ui.ActionBar.AlertDialog;
|
||||||
import org.telegram.ui.ActionBar.BaseFragment;
|
import org.telegram.ui.ActionBar.BaseFragment;
|
||||||
import org.telegram.ui.ActionBar.DrawerLayoutContainer;
|
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.DrawerAddCell;
|
||||||
import org.telegram.ui.Cells.DrawerUserCell;
|
import org.telegram.ui.Cells.DrawerUserCell;
|
||||||
import org.telegram.ui.Cells.LanguageCell;
|
import org.telegram.ui.Cells.LanguageCell;
|
||||||
import org.telegram.ui.Components.AudioPlayerAlert;
|
|
||||||
import org.telegram.ui.Components.AlertsCreator;
|
import org.telegram.ui.Components.AlertsCreator;
|
||||||
|
import org.telegram.ui.Components.AudioPlayerAlert;
|
||||||
import org.telegram.ui.Components.BlockingUpdateView;
|
import org.telegram.ui.Components.BlockingUpdateView;
|
||||||
import org.telegram.ui.Components.ChatActivityEnterView;
|
import org.telegram.ui.Components.ChatActivityEnterView;
|
||||||
import org.telegram.ui.Components.Easings;
|
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.SharingLocationsAlert;
|
||||||
import org.telegram.ui.Components.SideMenultItemAnimator;
|
import org.telegram.ui.Components.SideMenultItemAnimator;
|
||||||
import org.telegram.ui.Components.StickersAlert;
|
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.TermsOfServiceView;
|
||||||
import org.telegram.ui.Components.ThemeEditorView;
|
import org.telegram.ui.Components.ThemeEditorView;
|
||||||
import org.telegram.ui.Components.UpdateAppAlertDialog;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -113,11 +118,17 @@ import java.util.Map;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
import tw.nekomimi.nekogram.NekoConfig;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import tw.nekomimi.nekogram.NekoXConfig;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import tw.nekomimi.nekogram.NekoXSettingActivity;
|
||||||
|
|
||||||
import tw.nekomimi.nekogram.settings.NekoSettingsActivity;
|
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 {
|
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();
|
Uri uri = intent.getData();
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
String url = uri.toString().toLowerCase();
|
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);
|
long crashed_time = preferences.getLong("intro_crashed_time", 0);
|
||||||
boolean fromIntro = intent.getBooleanExtra("fromIntro", false);
|
boolean fromIntro = intent.getBooleanExtra("fromIntro", false);
|
||||||
if (fromIntro) {
|
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) {
|
if (!isProxy && Math.abs(crashed_time - System.currentTimeMillis()) >= 60 * 2 * 1000 && intent != null && !fromIntro) {
|
||||||
preferences = ApplicationLoader.applicationContext.getSharedPreferences("logininfo2", MODE_PRIVATE);
|
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 {
|
try {
|
||||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
|
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -511,10 +527,8 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
||||||
sideMenu.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
|
sideMenu.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
|
||||||
sideMenu.setAllowItemsInteractionDuringAnimation(false);
|
sideMenu.setAllowItemsInteractionDuringAnimation(false);
|
||||||
sideMenu.setAdapter(drawerLayoutAdapter = new DrawerLayoutAdapter(this, itemAnimator));
|
sideMenu.setAdapter(drawerLayoutAdapter = new DrawerLayoutAdapter(this, itemAnimator));
|
||||||
|
|
||||||
ItemTouchHelper drawerItemTouchHelper = new ItemTouchHelper(new DrawerItemTouchHelperCallback());
|
ItemTouchHelper drawerItemTouchHelper = new ItemTouchHelper(new DrawerItemTouchHelperCallback());
|
||||||
drawerItemTouchHelper.attachToRecyclerView(sideMenu);
|
drawerItemTouchHelper.attachToRecyclerView(sideMenu);
|
||||||
|
|
||||||
drawerLayoutContainer.setDrawerLayout(sideMenu);
|
drawerLayoutContainer.setDrawerLayout(sideMenu);
|
||||||
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) sideMenu.getLayoutParams();
|
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) sideMenu.getLayoutParams();
|
||||||
Point screenSize = AndroidUtilities.getRealScreenSize();
|
Point screenSize = AndroidUtilities.getRealScreenSize();
|
||||||
|
@ -539,6 +553,52 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
||||||
presentFragment(new LoginActivity(freeAccount));
|
presentFragment(new LoginActivity(freeAccount));
|
||||||
}
|
}
|
||||||
drawerLayoutContainer.closeDrawer(false);
|
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 {
|
} else {
|
||||||
int id = drawerLayoutAdapter.getId(position);
|
int id = drawerLayoutAdapter.getId(position);
|
||||||
if (id == 2) {
|
if (id == 2) {
|
||||||
|
@ -562,7 +622,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
||||||
presentFragment(new ChannelCreateActivity(args));
|
presentFragment(new ChannelCreateActivity(args));
|
||||||
} else {
|
} else {
|
||||||
presentFragment(new ActionIntroActivity(ActionIntroActivity.ACTION_TYPE_CHANNEL_CREATE));
|
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);
|
drawerLayoutContainer.closeDrawer(false);
|
||||||
} else if (id == 6) {
|
} else if (id == 6) {
|
||||||
|
@ -576,7 +636,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
||||||
presentFragment(fragment);
|
presentFragment(fragment);
|
||||||
drawerLayoutContainer.closeDrawer(false);
|
drawerLayoutContainer.closeDrawer(false);
|
||||||
} else if (id == 9) {
|
} else if (id == 9) {
|
||||||
Browser.openUrl(LaunchActivity.this, LocaleController.getString("TelegramFaqUrl", R.string.TelegramFaqUrl));
|
Browser.openUrl(LaunchActivity.this, NekoXConfig.FAQ_URL);
|
||||||
drawerLayoutContainer.closeDrawer(false);
|
drawerLayoutContainer.closeDrawer(false);
|
||||||
} else if (id == 10) {
|
} else if (id == 10) {
|
||||||
presentFragment(new CallLogActivity());
|
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.didSetNewWallpapper);
|
||||||
NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.notificationsCountUpdated);
|
NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.notificationsCountUpdated);
|
||||||
NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.screenStateChanged);
|
NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.screenStateChanged);
|
||||||
|
NotificationCenter.getGlobalInstance().addObserver(drawerLayoutAdapter, NotificationCenter.proxySettingsChanged);
|
||||||
|
|
||||||
if (actionBarLayout.fragmentsStack.isEmpty()) {
|
if (actionBarLayout.fragmentsStack.isEmpty()) {
|
||||||
if (!UserConfig.getInstance(currentAccount).isClientActivated()) {
|
if (!UserConfig.getInstance(currentAccount).isClientActivated()) {
|
||||||
|
@ -745,7 +806,26 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
||||||
FileLog.e(e);
|
FileLog.e(e);
|
||||||
}
|
}
|
||||||
MediaController.getInstance().setBaseActivity(this, true);
|
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() {
|
private void checkSystemBarColors() {
|
||||||
|
@ -765,6 +845,10 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
||||||
}
|
}
|
||||||
|
|
||||||
public void switchToAccount(int account, boolean removeAll) {
|
public void switchToAccount(int account, boolean removeAll) {
|
||||||
|
switchToAccount(account, removeAll, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void switchToAccount(int account, boolean removeAll, boolean afterLogin) {
|
||||||
if (account == UserConfig.selectedAccount) {
|
if (account == UserConfig.selectedAccount) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -806,6 +890,9 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
||||||
if (UserConfig.getInstance(account).unacceptedTermsOfService != null) {
|
if (UserConfig.getInstance(account).unacceptedTermsOfService != null) {
|
||||||
showTosActivity(account, UserConfig.getInstance(account).unacceptedTermsOfService);
|
showTosActivity(account, UserConfig.getInstance(account).unacceptedTermsOfService);
|
||||||
}
|
}
|
||||||
|
if (afterLogin) {
|
||||||
|
PrivacyUtil.postCheckAll(this, account);
|
||||||
|
}
|
||||||
updateCurrentConnectionState(currentAccount);
|
updateCurrentConnectionState(currentAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1117,15 +1204,14 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
||||||
if (!TextUtils.isEmpty(text)) {
|
if (!TextUtils.isEmpty(text)) {
|
||||||
Matcher m = locationRegex.matcher(text);
|
Matcher m = locationRegex.matcher(text);
|
||||||
if (m.find()) {
|
if (m.find()) {
|
||||||
String lines[] = text.split("\\n");
|
String[] lines = text.split("\\n");
|
||||||
String venueTitle = null;
|
String venueTitle = null;
|
||||||
String venueAddress = null;
|
String venueAddress = null;
|
||||||
if (lines[0].equals("My Position")){
|
if (lines[0].equals("My Position")) {
|
||||||
// Use normal GeoPoint message (user position)
|
// Use normal GeoPoint message (user position)
|
||||||
}
|
} else if (!lines[0].contains("geo:")) {
|
||||||
else if(!lines[0].contains("geo:")){
|
|
||||||
venueTitle = lines[0];
|
venueTitle = lines[0];
|
||||||
if(!lines[1].contains("geo:")){
|
if (!lines[1].contains("geo:")) {
|
||||||
venueAddress = lines[1];
|
venueAddress = lines[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1151,11 +1237,6 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
||||||
parcelable = Uri.parse(parcelable.toString());
|
parcelable = Uri.parse(parcelable.toString());
|
||||||
}
|
}
|
||||||
Uri uri = (Uri) parcelable;
|
Uri uri = (Uri) parcelable;
|
||||||
if (uri != null) {
|
|
||||||
if (AndroidUtilities.isInternalUri(uri)) {
|
|
||||||
error = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!error) {
|
if (!error) {
|
||||||
if (uri != null && (type != null && type.startsWith("image/") || uri.toString().toLowerCase().endsWith(".jpg"))) {
|
if (uri != null && (type != null && type.startsWith("image/") || uri.toString().toLowerCase().endsWith(".jpg"))) {
|
||||||
if (photoPathsArray == null) {
|
if (photoPathsArray == null) {
|
||||||
|
@ -1642,6 +1723,8 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
||||||
open_settings = 3;
|
open_settings = 3;
|
||||||
} else if (url.contains("folders")) {
|
} else if (url.contains("folders")) {
|
||||||
open_settings = 4;
|
open_settings = 4;
|
||||||
|
} else if (url.contains("nekox")) {
|
||||||
|
open_settings = 101;
|
||||||
} else if (url.contains("neko")) {
|
} else if (url.contains("neko")) {
|
||||||
open_settings = 100;
|
open_settings = 100;
|
||||||
}
|
}
|
||||||
|
@ -1651,6 +1734,17 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
FileLog.e(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 {
|
} else {
|
||||||
unsupportedUrl = url.replace("tg://", "").replace("tg:", "");
|
unsupportedUrl = url.replace("tg://", "").replace("tg:", "");
|
||||||
int index;
|
int index;
|
||||||
|
@ -1849,6 +1943,12 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
||||||
fragment = new FiltersSetupActivity();
|
fragment = new FiltersSetupActivity();
|
||||||
} else if (open_settings == 100) {
|
} else if (open_settings == 100) {
|
||||||
fragment = new NekoSettingsActivity();
|
fragment = new NekoSettingsActivity();
|
||||||
|
} else if (open_settings == 101) {
|
||||||
|
if (NekoXConfig.developerMode) {
|
||||||
|
fragment = new NekoXSettingActivity();
|
||||||
|
} else {
|
||||||
|
fragment = new NekoSettingsActivity();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
fragment = null;
|
fragment = null;
|
||||||
}
|
}
|
||||||
|
@ -1949,7 +2049,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
||||||
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.didReceiveSmsCode, code);
|
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.didReceiveSmsCode, code);
|
||||||
} else {
|
} else {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(LaunchActivity.this);
|
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.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("OtherLoginCode", R.string.OtherLoginCode, code)));
|
||||||
builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null);
|
builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null);
|
||||||
showAlertDialog(builder);
|
showAlertDialog(builder);
|
||||||
|
@ -2178,7 +2278,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(LaunchActivity.this);
|
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")) {
|
if (error.text.startsWith("FLOOD_WAIT")) {
|
||||||
builder.setMessage(LocaleController.getString("FloodWait", R.string.FloodWait));
|
builder.setMessage(LocaleController.getString("FloodWait", R.string.FloodWait));
|
||||||
} else {
|
} else {
|
||||||
|
@ -2232,7 +2332,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(LaunchActivity.this);
|
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")) {
|
if (error.text.startsWith("FLOOD_WAIT")) {
|
||||||
builder.setMessage(LocaleController.getString("FloodWait", R.string.FloodWait));
|
builder.setMessage(LocaleController.getString("FloodWait", R.string.FloodWait));
|
||||||
} else if (error.text.equals("USERS_TOO_MUCH")) {
|
} 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) {
|
if (response instanceof TLRPC.TL_langPackLanguage) {
|
||||||
TLRPC.TL_langPackLanguage res = (TLRPC.TL_langPackLanguage) response;
|
TLRPC.TL_langPackLanguage res = (TLRPC.TL_langPackLanguage) response;
|
||||||
showAlertDialog(AlertsCreator.createLanguageAlert(LaunchActivity.this, res));
|
AlertsCreator.createLanguageAlert(LaunchActivity.this, res).show();
|
||||||
} else if (error != null) {
|
} else if (error != null) {
|
||||||
if ("LANG_CODE_NOT_SUPPORTED".equals(error.text)) {
|
if ("LANG_CODE_NOT_SUPPORTED".equals(error.text)) {
|
||||||
showAlertDialog(AlertsCreator.createSimpleAlert(LaunchActivity.this, LocaleController.getString("LanguageUnsupportedError", R.string.LanguageUnsupportedError)));
|
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) {
|
public AlertDialog showAlertDialog(AlertDialog.Builder builder) {
|
||||||
try {
|
try {
|
||||||
if (visibleDialog != null) {
|
if (visibleDialog != null) {
|
||||||
|
@ -2605,12 +2662,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
||||||
}
|
}
|
||||||
localeDialog = null;
|
localeDialog = null;
|
||||||
} else if (visibleDialog == proxyErrorDialog) {
|
} else if (visibleDialog == proxyErrorDialog) {
|
||||||
SharedPreferences preferences = MessagesController.getGlobalMainSettings();
|
SharedConfig.setProxyEnable(false);
|
||||||
SharedPreferences.Editor editor = MessagesController.getGlobalMainSettings().edit();
|
|
||||||
editor.putBoolean("proxy_enabled", false);
|
|
||||||
editor.putBoolean("proxy_enabled_calls", false);
|
|
||||||
editor.commit();
|
|
||||||
ConnectionsManager.setProxySettings(false, "", 1080, "", "", "");
|
|
||||||
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.proxySettingsChanged);
|
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.proxySettingsChanged);
|
||||||
proxyErrorDialog = null;
|
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.didSetPasscode);
|
||||||
NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.notificationsCountUpdated);
|
NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.notificationsCountUpdated);
|
||||||
NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.screenStateChanged);
|
NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.screenStateChanged);
|
||||||
|
NotificationCenter.getGlobalInstance().removeObserver(drawerLayoutAdapter, NotificationCenter.proxySettingsChanged);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void presentFragment(BaseFragment fragment) {
|
public void presentFragment(BaseFragment fragment) {
|
||||||
|
@ -2928,7 +2982,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
||||||
|
|
||||||
private void showPermissionErrorAlert(String message) {
|
private void showPermissionErrorAlert(String message) {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
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.setMessage(message);
|
||||||
builder.setNegativeButton(LocaleController.getString("PermissionOpenSettings", R.string.PermissionOpenSettings), (dialog, which) -> {
|
builder.setNegativeButton(LocaleController.getString("PermissionOpenSettings", R.string.PermissionOpenSettings), (dialog, which) -> {
|
||||||
try {
|
try {
|
||||||
|
@ -2953,13 +3007,18 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
||||||
ApplicationLoader.mainInterfacePausedStageQueueTime = 0;
|
ApplicationLoader.mainInterfacePausedStageQueueTime = 0;
|
||||||
});
|
});
|
||||||
onPasscodePause();
|
onPasscodePause();
|
||||||
actionBarLayout.onPause();
|
try {
|
||||||
if (AndroidUtilities.isTablet()) {
|
if (actionBarLayout != null) {
|
||||||
rightActionBarLayout.onPause();
|
actionBarLayout.onPause();
|
||||||
layersActionBarLayout.onPause();
|
}
|
||||||
}
|
if (AndroidUtilities.isTablet()) {
|
||||||
if (passcodeView != null) {
|
rightActionBarLayout.onPause();
|
||||||
passcodeView.onPause();
|
layersActionBarLayout.onPause();
|
||||||
|
}
|
||||||
|
if (passcodeView != null) {
|
||||||
|
passcodeView.onPause();
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
ConnectionsManager.getInstance(currentAccount).setAppPaused(true, false);
|
ConnectionsManager.getInstance(currentAccount).setAppPaused(true, false);
|
||||||
if (PhotoViewer.hasInstance() && PhotoViewer.getInstance().isVisible()) {
|
if (PhotoViewer.hasInstance() && PhotoViewer.getInstance().isVisible()) {
|
||||||
|
@ -3061,6 +3120,9 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
||||||
}
|
}
|
||||||
ConnectionsManager.getInstance(currentAccount).setAppPaused(false, false);
|
ConnectionsManager.getInstance(currentAccount).setAppPaused(false, false);
|
||||||
updateCurrentConnectionState(currentAccount);
|
updateCurrentConnectionState(currentAccount);
|
||||||
|
if (NekoConfig.disableProxyWhenVpnEnabled && SharedConfig.proxyEnabled && ProxyUtil.isVPNEnabled()) {
|
||||||
|
SharedConfig.setProxyEnable(false);
|
||||||
|
}
|
||||||
if (PhotoViewer.hasInstance() && PhotoViewer.getInstance().isVisible()) {
|
if (PhotoViewer.hasInstance() && PhotoViewer.getInstance().isVisible()) {
|
||||||
PhotoViewer.getInstance().onResume();
|
PhotoViewer.getInstance().onResume();
|
||||||
}
|
}
|
||||||
|
@ -3076,7 +3138,6 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
||||||
} else if (UserConfig.getInstance(0).pendingAppUpdate != null) {
|
} else if (UserConfig.getInstance(0).pendingAppUpdate != null) {
|
||||||
showUpdateActivity(UserConfig.selectedAccount, UserConfig.getInstance(0).pendingAppUpdate, true);
|
showUpdateActivity(UserConfig.selectedAccount, UserConfig.getInstance(0).pendingAppUpdate, true);
|
||||||
}
|
}
|
||||||
checkAppUpdate(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -3141,7 +3202,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
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) {
|
if (reason != 2 && reason != 3) {
|
||||||
builder.setNegativeButton(LocaleController.getString("MoreInfo", R.string.MoreInfo), (dialogInterface, i) -> {
|
builder.setNegativeButton(LocaleController.getString("MoreInfo", R.string.MoreInfo), (dialogInterface, i) -> {
|
||||||
if (!mainFragmentsStack.isEmpty()) {
|
if (!mainFragmentsStack.isEmpty()) {
|
||||||
|
@ -3180,7 +3241,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
||||||
} else if (id == NotificationCenter.wasUnableToFindCurrentLocation) {
|
} else if (id == NotificationCenter.wasUnableToFindCurrentLocation) {
|
||||||
final HashMap<String, MessageObject> waitingForLocation = (HashMap<String, MessageObject>) args[0];
|
final HashMap<String, MessageObject> waitingForLocation = (HashMap<String, MessageObject>) args[0];
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
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.setPositiveButton(LocaleController.getString("OK", R.string.OK), null);
|
||||||
builder.setNegativeButton(LocaleController.getString("ShareYouLocationUnableManually", R.string.ShareYouLocationUnableManually), (dialogInterface, i) -> {
|
builder.setNegativeButton(LocaleController.getString("ShareYouLocationUnableManually", R.string.ShareYouLocationUnableManually), (dialogInterface, i) -> {
|
||||||
if (mainFragmentsStack.isEmpty()) {
|
if (mainFragmentsStack.isEmpty()) {
|
||||||
|
@ -3211,7 +3272,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (id == NotificationCenter.didSetPasscode) {
|
} else if (id == NotificationCenter.didSetPasscode) {
|
||||||
if (SharedConfig.passcodeHash.length() > 0 && !SharedConfig.allowScreenCapture) {
|
if (SharedConfig.passcodeHash.length() > 0 && !SharedConfig.allowScreenCapture && !NekoXConfig.disableFlagSecure) {
|
||||||
try {
|
try {
|
||||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
|
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -3290,7 +3351,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
||||||
themeSwitchImageView.setVisibility(View.VISIBLE);
|
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])));
|
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);
|
Animator anim = ViewAnimationUtils.createCircularReveal(drawerLayoutContainer, pos[0], pos[1], 0, finalRadius);
|
||||||
anim.setDuration(400);
|
anim.setDuration(100);
|
||||||
anim.setInterpolator(Easings.easeInOutQuad);
|
anim.setInterpolator(Easings.easeInOutQuad);
|
||||||
anim.addListener(new AnimatorListenerAdapter() {
|
anim.addListener(new AnimatorListenerAdapter() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -3465,7 +3526,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
||||||
freeSpace = statFs.getAvailableBlocksLong() * statFs.getBlockSizeLong();
|
freeSpace = statFs.getAvailableBlocksLong() * statFs.getBlockSizeLong();
|
||||||
}
|
}
|
||||||
if (freeSpace < 1024 * 1024 * 100) {
|
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(() -> {
|
AndroidUtilities.runOnUIThread(() -> {
|
||||||
try {
|
try {
|
||||||
AlertsCreator.createFreeSpaceDialog(LaunchActivity.this).show();
|
AlertsCreator.createFreeSpaceDialog(LaunchActivity.this).show();
|
||||||
|
@ -3532,7 +3593,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
||||||
});
|
});
|
||||||
localeDialog = showAlertDialog(builder);
|
localeDialog = showAlertDialog(builder);
|
||||||
SharedPreferences preferences = MessagesController.getGlobalMainSettings();
|
SharedPreferences preferences = MessagesController.getGlobalMainSettings();
|
||||||
preferences.edit().putString("language_showed2", systemLang).commit();
|
preferences.edit().putString("language_showed2", systemLang).apply();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
FileLog.e(e);
|
FileLog.e(e);
|
||||||
}
|
}
|
||||||
|
@ -3799,33 +3860,36 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
if (passcodeView.getVisibility() == View.VISIBLE) {
|
try {
|
||||||
finish();
|
if (passcodeView != null && passcodeView.getVisibility() == View.VISIBLE) {
|
||||||
return;
|
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
if (SecretMediaViewer.hasInstance() && SecretMediaViewer.getInstance().isVisible()) {
|
||||||
actionBarLayout.onBackPressed();
|
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()) {
|
if (ArticleViewer.hasInstance() && ArticleViewer.getInstance().isVisible()) {
|
||||||
ArticleViewer.getInstance().close(false, true);
|
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);
|
drawerLayoutContainer.setAllowOpenDrawer(!(fragment instanceof LoginActivity || fragment instanceof CountrySelectActivity) && layersActionBarLayout.getVisibility() != View.VISIBLE, true);
|
||||||
if (fragment instanceof DialogsActivity) {
|
if (fragment instanceof DialogsActivity) {
|
||||||
DialogsActivity dialogsActivity = (DialogsActivity) fragment;
|
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.ClipData;
|
||||||
import android.content.ClipboardManager;
|
import android.content.ClipboardManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
|
@ -41,8 +40,6 @@ import android.widget.LinearLayout;
|
||||||
import android.widget.ScrollView;
|
import android.widget.ScrollView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.core.graphics.ColorUtils;
|
|
||||||
|
|
||||||
import org.telegram.messenger.AndroidUtilities;
|
import org.telegram.messenger.AndroidUtilities;
|
||||||
import org.telegram.messenger.LocaleController;
|
import org.telegram.messenger.LocaleController;
|
||||||
import org.telegram.messenger.MessagesController;
|
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.BaseFragment;
|
||||||
import org.telegram.ui.ActionBar.Theme;
|
import org.telegram.ui.ActionBar.Theme;
|
||||||
import org.telegram.ui.ActionBar.ThemeDescription;
|
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.ShadowSectionCell;
|
||||||
import org.telegram.ui.Cells.TextInfoPrivacyCell;
|
import org.telegram.ui.Cells.TextInfoPrivacyCell;
|
||||||
import org.telegram.ui.Cells.TextSettingsCell;
|
import org.telegram.ui.Cells.TextSettingsCell;
|
||||||
|
@ -67,7 +62,6 @@ import org.telegram.ui.Components.LayoutHelper;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.URLDecoder;
|
import java.net.URLDecoder;
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public class ProxySettingsActivity extends BaseFragment {
|
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_USER = 2;
|
||||||
private final static int FIELD_PASSWORD = 3;
|
private final static int FIELD_PASSWORD = 3;
|
||||||
private final static int FIELD_SECRET = 4;
|
private final static int FIELD_SECRET = 4;
|
||||||
|
private final static int FIELD_REMARKS = 5;
|
||||||
|
|
||||||
private EditTextBoldCursor[] inputFields;
|
private EditTextBoldCursor[] inputFields;
|
||||||
private ScrollView scrollView;
|
private ScrollView scrollView;
|
||||||
private LinearLayout linearLayout2;
|
private LinearLayout linearLayout2;
|
||||||
private LinearLayout inputFieldsContainer;
|
private LinearLayout inputFieldsContainer;
|
||||||
private HeaderCell headerCell;
|
|
||||||
private ShadowSectionCell[] sectionCell = new ShadowSectionCell[3];
|
private ShadowSectionCell[] sectionCell = new ShadowSectionCell[3];
|
||||||
private TextInfoPrivacyCell[] bottomCells = new TextInfoPrivacyCell[2];
|
private TextInfoPrivacyCell[] bottomCells = new TextInfoPrivacyCell[2];
|
||||||
private TextSettingsCell shareCell;
|
|
||||||
private TextSettingsCell pasteCell;
|
private TextSettingsCell pasteCell;
|
||||||
private ActionBarMenuItem doneItem;
|
private ActionBarMenuItem doneItem;
|
||||||
private RadioCell[] typeCell = new RadioCell[2];
|
// private RadioCell[] typeCell = new RadioCell[2];
|
||||||
private int currentType = -1;
|
private int currentType = -1;
|
||||||
|
|
||||||
private int pasteType = -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));
|
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 = 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);
|
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));
|
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();
|
super();
|
||||||
currentProxyInfo = new SharedConfig.ProxyInfo("", 1080, "", "", "");
|
currentProxyInfo = new SharedConfig.ProxyInfo("", 1080, "", "", "");
|
||||||
addingNewProxy = true;
|
addingNewProxy = true;
|
||||||
|
currentType = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProxySettingsActivity(SharedConfig.ProxyInfo proxyInfo) {
|
public ProxySettingsActivity(SharedConfig.ProxyInfo proxyInfo) {
|
||||||
|
@ -210,6 +204,7 @@ public class ProxySettingsActivity extends BaseFragment {
|
||||||
}
|
}
|
||||||
currentProxyInfo.address = inputFields[FIELD_IP].getText().toString();
|
currentProxyInfo.address = inputFields[FIELD_IP].getText().toString();
|
||||||
currentProxyInfo.port = Utilities.parseInt(inputFields[FIELD_PORT].getText().toString());
|
currentProxyInfo.port = Utilities.parseInt(inputFields[FIELD_PORT].getText().toString());
|
||||||
|
currentProxyInfo.setRemarks(inputFields[FIELD_REMARKS].getText().toString());
|
||||||
if (currentType == 0) {
|
if (currentType == 0) {
|
||||||
currentProxyInfo.secret = "";
|
currentProxyInfo.secret = "";
|
||||||
currentProxyInfo.username = inputFields[FIELD_USER].getText().toString();
|
currentProxyInfo.username = inputFields[FIELD_USER].getText().toString();
|
||||||
|
@ -222,14 +217,11 @@ public class ProxySettingsActivity extends BaseFragment {
|
||||||
|
|
||||||
SharedPreferences preferences = MessagesController.getGlobalMainSettings();
|
SharedPreferences preferences = MessagesController.getGlobalMainSettings();
|
||||||
SharedPreferences.Editor editor = preferences.edit();
|
SharedPreferences.Editor editor = preferences.edit();
|
||||||
boolean enabled;
|
|
||||||
if (addingNewProxy) {
|
if (addingNewProxy) {
|
||||||
SharedConfig.addProxy(currentProxyInfo);
|
SharedConfig.addProxy(currentProxyInfo);
|
||||||
SharedConfig.currentProxy = currentProxyInfo;
|
SharedConfig.setCurrentProxy(currentProxyInfo);
|
||||||
editor.putBoolean("proxy_enabled", true);
|
|
||||||
enabled = true;
|
|
||||||
} else {
|
} else {
|
||||||
enabled = preferences.getBoolean("proxy_enabled", false);
|
SharedConfig.setProxyEnable(false);
|
||||||
SharedConfig.saveProxyList();
|
SharedConfig.saveProxyList();
|
||||||
}
|
}
|
||||||
if (addingNewProxy || SharedConfig.currentProxy == currentProxyInfo) {
|
if (addingNewProxy || SharedConfig.currentProxy == currentProxyInfo) {
|
||||||
|
@ -238,13 +230,21 @@ public class ProxySettingsActivity extends BaseFragment {
|
||||||
editor.putString("proxy_user", currentProxyInfo.username);
|
editor.putString("proxy_user", currentProxyInfo.username);
|
||||||
editor.putInt("proxy_port", currentProxyInfo.port);
|
editor.putInt("proxy_port", currentProxyInfo.port);
|
||||||
editor.putString("proxy_secret", currentProxyInfo.secret);
|
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);
|
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.proxySettingsChanged);
|
||||||
|
|
||||||
finishFragment();
|
finishFragment();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -265,23 +265,7 @@ public class ProxySettingsActivity extends BaseFragment {
|
||||||
linearLayout2.setOrientation(LinearLayout.VERTICAL);
|
linearLayout2.setOrientation(LinearLayout.VERTICAL);
|
||||||
scrollView.addView(linearLayout2, new ScrollView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
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);
|
sectionCell[0] = new ShadowSectionCell(context);
|
||||||
linearLayout2.addView(sectionCell[0], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
|
|
||||||
|
|
||||||
inputFieldsContainer = new LinearLayout(context);
|
inputFieldsContainer = new LinearLayout(context);
|
||||||
inputFieldsContainer.setOrientation(LinearLayout.VERTICAL);
|
inputFieldsContainer.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
@ -293,8 +277,8 @@ public class ProxySettingsActivity extends BaseFragment {
|
||||||
}
|
}
|
||||||
linearLayout2.addView(inputFieldsContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
|
linearLayout2.addView(inputFieldsContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
|
||||||
|
|
||||||
inputFields = new EditTextBoldCursor[5];
|
inputFields = new EditTextBoldCursor[6];
|
||||||
for (int a = 0; a < 5; a++) {
|
for (int a = 0; a < 6; a++) {
|
||||||
FrameLayout container = new FrameLayout(context);
|
FrameLayout container = new FrameLayout(context);
|
||||||
inputFieldsContainer.addView(container, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 64));
|
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].setHintText(LocaleController.getString("UseProxySecret", R.string.UseProxySecret));
|
||||||
inputFields[a].setText(currentProxyInfo.secret);
|
inputFields[a].setText(currentProxyInfo.secret);
|
||||||
break;
|
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());
|
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));
|
linearLayout2.addView(sectionCell[2], 1, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
|
||||||
sectionCell[2].setVisibility(View.GONE);
|
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] = new ShadowSectionCell(context);
|
||||||
sectionCell[1].setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow));
|
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));
|
linearLayout2.addView(sectionCell[1], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
|
||||||
|
@ -556,8 +486,19 @@ public class ProxySettingsActivity extends BaseFragment {
|
||||||
shareDoneProgress = 1f;
|
shareDoneProgress = 1f;
|
||||||
checkShareDone(false);
|
checkShareDone(false);
|
||||||
|
|
||||||
currentType = -1;
|
if (currentType == -1) {
|
||||||
setProxyType(TextUtils.isEmpty(currentProxyInfo.secret) ? 0 : 1, false);
|
|
||||||
|
setProxyType(TextUtils.isEmpty(currentProxyInfo.secret) ? 0 : 1, false);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
int t = currentType;
|
||||||
|
|
||||||
|
currentType = -1;
|
||||||
|
|
||||||
|
setProxyType(t, false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
pasteType = -1;
|
pasteType = -1;
|
||||||
pasteString = null;
|
pasteString = null;
|
||||||
|
@ -665,7 +606,6 @@ public class ProxySettingsActivity extends BaseFragment {
|
||||||
shareDoneAnimator.setDuration(200);
|
shareDoneAnimator.setDuration(200);
|
||||||
shareDoneAnimator.addUpdateListener(a -> {
|
shareDoneAnimator.addUpdateListener(a -> {
|
||||||
shareDoneProgress = AndroidUtilities.lerp(shareDoneProgressAnimValues, a.getAnimatedFraction());
|
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);
|
doneItem.setAlpha(shareDoneProgress / 2f + 0.5f);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -675,17 +615,15 @@ public class ProxySettingsActivity extends BaseFragment {
|
||||||
shareDoneAnimator.start();
|
shareDoneAnimator.start();
|
||||||
} else {
|
} else {
|
||||||
shareDoneProgress = enabled ? 1f : 0f;
|
shareDoneProgress = enabled ? 1f : 0f;
|
||||||
shareCell.setTextColor(enabled ? Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4) : Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2));
|
|
||||||
doneItem.setAlpha(enabled ? 1f : .5f);
|
doneItem.setAlpha(enabled ? 1f : .5f);
|
||||||
}
|
}
|
||||||
shareCell.setEnabled(enabled);
|
|
||||||
doneItem.setEnabled(enabled);
|
doneItem.setEnabled(enabled);
|
||||||
shareDoneEnabled = enabled;
|
shareDoneEnabled = enabled;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkShareDone(boolean animated) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
setShareDoneEnabled(inputFields[FIELD_IP].length() != 0 && Utilities.parseInt(inputFields[FIELD_PORT].getText().toString()) != 0, animated);
|
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_PASSWORD].getParent()).setVisibility(View.GONE);
|
||||||
((View) inputFields[FIELD_USER].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
|
@Override
|
||||||
public ArrayList<ThemeDescription> getThemeDescriptions() {
|
public ArrayList<ThemeDescription> getThemeDescriptions() {
|
||||||
final ThemeDescription.ThemeDescriptionDelegate delegate = () -> {
|
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) {
|
if (inputFields != null) {
|
||||||
for (int i = 0; i < inputFields.length; i++) {
|
for (int i = 0; i < inputFields.length; i++) {
|
||||||
inputFields[i].setLineColors(Theme.getColor(Theme.key_windowBackgroundWhiteInputField),
|
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(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(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_windowBackgroundWhite));
|
||||||
arrayList.add(new ThemeDescription(pasteCell, ThemeDescription.FLAG_SELECTORWHITE, null, null, null, null, Theme.key_listSelector));
|
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));
|
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) {
|
if (inputFields != null) {
|
||||||
for (int a = 0; a < inputFields.length; a++) {
|
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_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_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(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++) {
|
for (int a = 0; a < sectionCell.length; a++) {
|
||||||
if (sectionCell[a] != null) {
|
if (sectionCell[a] != null) {
|
||||||
arrayList.add(new ThemeDescription(sectionCell[a], ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ShadowSectionCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow));
|
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