Unverified Commit e6b511f2 authored by Mygod's avatar Mygod Committed by GitHub

Merge pull request #2245 from shadowsocks/plugin-1.3.0

Plugin lib 1.3.0
parents 32e7d8d2 34387bb1
......@@ -5,7 +5,7 @@ apply plugin: 'com.github.ben-manes.versions'
buildscript {
ext {
javaVersion = JavaVersion.VERSION_1_8
kotlinVersion = '1.3.31'
kotlinVersion = '1.3.40'
minSdkVersion = 21
sdkVersion = 29
compileSdkVersion = 29
......
......@@ -59,8 +59,8 @@ dependencies {
api 'androidx.work:work-runtime-ktx:2.1.0-beta01'
api 'com.crashlytics.sdk.android:crashlytics:2.10.1'
api 'com.google.code.gson:gson:2.8.5'
api 'com.google.firebase:firebase-config:17.0.0'
api 'com.google.firebase:firebase-core:16.0.9'
api 'com.google.firebase:firebase-config:18.0.0'
api 'com.google.firebase:firebase-core:17.0.0'
api 'dnsjava:dnsjava:2.1.9'
api 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1'
api 'org.connectbot.jsocks:jsocks:1.0.0'
......
......@@ -25,6 +25,7 @@ import android.content.BroadcastReceiver
import android.content.ContentResolver
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ProviderInfo
import android.content.pm.Signature
import android.database.Cursor
import android.net.Uri
......@@ -41,6 +42,8 @@ import java.io.File
import java.io.FileNotFoundException
object PluginManager {
private const val TAG = "PluginManager"
class PluginNotFoundException(private val plugin: String) : FileNotFoundException(plugin) {
override fun getLocalizedMessage() = app.getString(com.github.shadowsocks.core.R.string.plugin_unknown, plugin)
}
......@@ -137,28 +140,51 @@ object PluginManager {
private fun initNative(options: PluginOptions): String? {
val providers = app.packageManager.queryIntentContentProviders(
Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(options.id)), 0)
Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(options.id)), PackageManager.GET_META_DATA)
if (providers.isEmpty()) return null
val uri = Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(providers.single().providerInfo.authority)
.build()
val provider = providers.single().providerInfo
var failure: Throwable? = null
try {
initNativeFaster(provider)?.also { return it }
} catch (t: Throwable) {
Crashlytics.log(Log.WARN, TAG, "Initializing native plugin faster mode failed")
failure = t
}
val uri = Uri.Builder().apply {
scheme(ContentResolver.SCHEME_CONTENT)
authority(provider.authority)
}.build()
val cr = app.contentResolver
return try {
initNativeFast(cr, options, uri)
try {
return initNativeFast(cr, options, uri)
} catch (t: Throwable) {
Crashlytics.log(Log.WARN, "PluginManager",
"Initializing native plugin fast mode failed. Falling back to slow mode.")
printLog(t)
initNativeSlow(cr, options, uri)
Crashlytics.log(Log.WARN, TAG, "Initializing native plugin fast mode failed")
failure?.also { t.addSuppressed(it) }
failure = t
}
try {
return initNativeSlow(cr, options, uri)
} catch (t: Throwable) {
failure?.also { t.addSuppressed(it) }
throw t
}
}
private fun initNativeFast(cr: ContentResolver, options: PluginOptions, uri: Uri): String {
val result = cr.call(uri, PluginContract.METHOD_GET_EXECUTABLE, null,
bundleOf(Pair(PluginContract.EXTRA_OPTIONS, options.id)))!!.getString(PluginContract.EXTRA_ENTRY)!!
check(File(result).canExecute())
return result
private fun initNativeFaster(provider: ProviderInfo): String? {
return provider.metaData.getString(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")
......
......@@ -60,9 +60,9 @@ androidExtensions {
dependencies {
implementation project(':core')
implementation 'androidx.browser:browser:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta1'
implementation 'com.google.android.gms:play-services-vision:17.0.2'
implementation 'com.google.firebase:firebase-ads:17.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2'
implementation 'com.google.android.gms:play-services-vision:18.0.0'
implementation 'com.google.firebase:firebase-ads:18.0.0'
implementation 'com.takisoft.preferencex:preferencex-simplemenu:1.0.0'
implementation 'com.twofortyfouram:android-plugin-api-for-locale:1.0.4'
implementation 'net.glxn.qrgen:android:2.0'
......
* 1.3.0:
* Optional new metadata `com.github.shadowsocks.plugin.executable_path` for even faster initialization;
(see doc for `PluginContract.METADATA_KEY_EXECUTABLE_PATH` for more information)
* Breaking API change: `val AlertDialogFragment.ret: Ret?` => `fun AlertDialogFragment.ret(which: Int): Ret?`;
(nothing needs to be done if you are not using this API)
* Dependency updates:
- Now targeting API 29;
- `androidx.core:core-ktx:1.1.0-rc01`;
- `com.google.android.material:material:1.1.0-alpha07`;
- `org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.40`.
* 1.2.0:
* New helper class `AlertDialogFragment` for creating `AlertDialog` that persists through configuration changes;
* Dependency update: `com.google.android.material:material:1.1.0-alpha03`.
......
......@@ -136,8 +136,8 @@ Every native mode plugin MUST have a content provider to provide the native exec
* MUST implement `query` that returns the file list which MUST include `$PLUGIN_ID` when having
these as arguments:
- `uri = "content://$authority_of_your_provider`;
- `projection = ["path", "mode"]`; (relative path, for example `obfs-local`; file mode, for
example `755`)
- `projection = ["path", "mode"]`; (relative path, for example `obfs-local`; file mode as integer, for
example `0b110100100`)
- `selection = null`;
- `selectionArgs = null`;
- `sortOrder = null`;
......@@ -186,10 +186,19 @@ If your plugin binary executable can run in place, you can support native mode w
`com.github.shadowsocks.plugin.EXTRA_ENTRY` when having `method = "shadowsocks:getExecutable"`;
(`com.github.shadowsocks.plugin.EXTRA_OPTIONS` is provided in extras as well just in case you
need them)
* SHOULD define `android:installLocation="internalOnly"` for `<manifest>` in AndroidManifest.xml;
* SHOULD define `android:extractNativeLibs="true"` for `<application>` in AndroidManifest.xml;
If you don't plan to support this mode, you can just throw `UnsupportedOperationException` when
being invoked. It will fallback to the slow routine automatically.
### Native mode without binary copying and setup
Additionally, if your plugin only needs to supply the path of your executable without doing any extra setup work,
you can use an additional `meta-data` with name `com.github.shadowsocks.plugin.executable_path`
to supply executable path to your native binary.
This allows the host app to launch your plugin without ever launching your app.
## JVM mode
This feature hasn't been implemented yet.
......@@ -229,7 +238,7 @@ Plugin app must include this in their application tag: (which should be automati
```
<meta-data android:name="com.github.shadowsocks.plugin.version"
android:value="0.0.2"/>
android:value="1.0.0"/>
```
# Android TV
......
GROUP=com.github.shadowsocks
VERSION_NAME=1.2.0
VERSION_CODE=9
VERSION_NAME=1.3.0
VERSION_CODE=10
POM_ARTIFACT_ID=plugin
POM_NAME=Shadowsocks Plugin
......
......@@ -3,6 +3,6 @@
<application
android:theme="@style/Theme.Shadowsocks">
<meta-data android:name="com.github.shadowsocks.plugin.version"
android:value="0.1.0"/>
android:value="1.3.0"/>
</application>
</manifest>
......@@ -69,7 +69,11 @@ abstract class NativePluginProvider : ContentProvider() {
}
/**
* Returns executable entry absolute path. This is used if plugin is sharing UID with the host.
* 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.
......
......@@ -84,6 +84,23 @@ object PluginContract {
* 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"
......@@ -99,11 +116,11 @@ object PluginContract {
*/
const val COLUMN_PATH = "path"
/**
* File mode bits. Default value is "644".
* File mode bits. Default value is 644 in octal.
*
* Example: "755"
* Example: 0b110100100 (for 755 in octal)
*
* Type: String
* Type: Int or String (deprecated)
*/
const val COLUMN_MODE = "mode"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment