Skip to content

Commit

Permalink
Introduce faster native plugin initialization
Browse files Browse the repository at this point in the history
Please read doc for METADATA_KEY_EXECUTABLE_PATH for more details.
Basically, we get around application launching limitations from Huawei (among others) by reading metadata and therefore prevent launching the application altogether.
This should hopefully fix shadowsocks#1091, shadowsocks#1106. (requires plugin app support)

This commit also refines error reporting in PluginManager.
  • Loading branch information
Mygod committed Jun 19, 2019
1 parent a291580 commit 16823f2
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 16 deletions.
58 changes: 42 additions & 16 deletions core/src/main/java/com/github/shadowsocks/plugin/PluginManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
Expand Down Expand Up @@ -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, TAG, "Initializing native plugin fast mode failed")
failure?.also { t.addSuppressed(it) }
failure = t
}

try {
return initNativeSlow(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)
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")
Expand Down
2 changes: 2 additions & 0 deletions plugin/CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
* 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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,19 @@ 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.
* 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"

Expand Down

0 comments on commit 16823f2

Please sign in to comment.