mirror of
https://github.com/oxyroid/M3UAndroid.git
synced 2025-05-17 19:35:58 +08:00
refactor: extension system.
This commit is contained in:
@ -41,7 +41,7 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":data:extension"))
|
||||
implementation(project(":extension:api"))
|
||||
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||
|
@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<permission
|
||||
android:name="com.m3u.permission.CONNECT_EXTENSION_PLUGIN"
|
||||
android:protectionLevel="normal" />
|
||||
<uses-permission android:name="com.m3u.permission.CONNECT_EXTENSION_PLUGIN"/>
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
@ -21,16 +18,6 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name="com.m3u.data.extension.RemoteServer"
|
||||
android:exported="true"
|
||||
android:permission="com.m3u.permission.CONNECT_EXTENSION_PLUGIN">
|
||||
<intent-filter>
|
||||
<action android:name="com.m3u.permission.CONNECT_EXTENSION_PLUGIN" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
</application>
|
||||
<queries>
|
||||
<package android:name="com.m3u.smartphone" />
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.m3u.extension
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
@ -18,6 +19,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.m3u.data.extension.Const
|
||||
import com.m3u.data.extension.RemoteClient
|
||||
import com.m3u.extension.ui.theme.M3UTheme
|
||||
import kotlinx.coroutines.launch
|
||||
@ -28,6 +30,8 @@ class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
val callToken = handleArguments(intent)
|
||||
|
||||
setContent {
|
||||
M3UTheme {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
@ -42,20 +46,33 @@ class MainActivity : ComponentActivity() {
|
||||
) {
|
||||
Button(
|
||||
onClick = {
|
||||
if (!isConnected) client.connect(this@MainActivity)
|
||||
else client.disconnect(this@MainActivity)
|
||||
val callToken = callToken
|
||||
if (!isConnected && callToken != null) {
|
||||
client.connect(
|
||||
context = this@MainActivity,
|
||||
targetPackageName = callToken.packageName,
|
||||
targetClassName = callToken.className,
|
||||
targetPermission = callToken.permission,
|
||||
accessKey = callToken.accessKey
|
||||
)
|
||||
} else {
|
||||
client.disconnect(this@MainActivity)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = if (isConnected) "Disconnect" else "Connect"
|
||||
text = when {
|
||||
isConnected -> "Disconnect"
|
||||
else -> "Connect"
|
||||
}
|
||||
)
|
||||
}
|
||||
Button(
|
||||
enabled = isConnected,
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
channelCount = client.call("test", "read-channel-count", "{}")
|
||||
?.toIntOrNull() ?: -1
|
||||
// channelCount = client.call("test", "read-channel-count", "{}")
|
||||
// ?.toIntOrNull() ?: -1
|
||||
}
|
||||
}
|
||||
) {
|
||||
@ -68,4 +85,19 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleArguments(intent: Intent): CallToken? {
|
||||
val packageName = intent.getStringExtra(Const.PACKAGE_NAME) ?: return null
|
||||
val className = intent.getStringExtra(Const.CLASS_NAME) ?: return null
|
||||
val permission = intent.getStringExtra(Const.PERMISSION) ?: return null
|
||||
val accessKey = intent.getStringExtra(Const.ACCESS_KEY) ?: return null
|
||||
return CallToken(packageName, className, permission, accessKey)
|
||||
}
|
||||
}
|
||||
|
||||
private data class CallToken(
|
||||
val packageName: String,
|
||||
val className: String,
|
||||
val permission: String,
|
||||
val accessKey: String
|
||||
)
|
@ -1,16 +0,0 @@
|
||||
package com.m3u.extension
|
||||
|
||||
import android.util.Log
|
||||
import com.google.auto.service.AutoService
|
||||
import com.m3u.data.extension.IRemoteCallback
|
||||
import com.m3u.data.extension.OnRemoteServerCall
|
||||
|
||||
@AutoService(OnRemoteServerCall::class)
|
||||
class OnRemoteServiceCallImpl: OnRemoteServerCall {
|
||||
override fun onCall(module: String, method: String, param: String, callback: IRemoteCallback?) {
|
||||
Log.d(TAG, "onCall: $module, $method, $param, $callback")
|
||||
}
|
||||
companion object {
|
||||
private const val TAG = "OnRemoteServiceCallImpl"
|
||||
}
|
||||
}
|
@ -122,7 +122,8 @@ dependencies {
|
||||
implementation(project(":core"))
|
||||
implementation(project(":core:foundation"))
|
||||
implementation(project(":data"))
|
||||
implementation(project(":data:extension"))
|
||||
implementation(project(":extension:api"))
|
||||
implementation(project(":extension:runtime"))
|
||||
// business
|
||||
implementation(project(":business:foryou"))
|
||||
implementation(project(":business:favorite"))
|
||||
|
@ -15,7 +15,6 @@
|
||||
<permission
|
||||
android:name="com.m3u.permission.CONNECT_EXTENSION_PLUGIN"
|
||||
android:protectionLevel="normal" />
|
||||
<uses-permission android:name="com.m3u.permission.CONNECT_EXTENSION_PLUGIN"/>
|
||||
|
||||
<uses-feature
|
||||
android:name="android.software.leanback"
|
||||
@ -76,7 +75,7 @@
|
||||
android:theme="@style/Theme.M3U" />
|
||||
|
||||
<service
|
||||
android:name="com.m3u.data.extension.RemoteServer"
|
||||
android:name="com.m3u.data.extension.RemoteService"
|
||||
android:exported="true"
|
||||
android:permission="com.m3u.permission.CONNECT_EXTENSION_PLUGIN">
|
||||
<intent-filter>
|
||||
|
@ -14,6 +14,7 @@ plugins {
|
||||
alias(libs.plugins.org.jetbrains.kotlin.serialization) apply false
|
||||
alias(libs.plugins.org.jetbrains.kotlin.jvm) apply false
|
||||
alias(libs.plugins.androidx.baselineprofile) apply false
|
||||
id("com.squareup.wire") version "4.9.2" apply false
|
||||
}
|
||||
|
||||
subprojects {
|
||||
|
@ -26,7 +26,7 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation(project(":core"))
|
||||
implementation(project(":data:extension"))
|
||||
implementation(project(":extension:api"))
|
||||
implementation(project(":lint:annotation"))
|
||||
ksp(project(":lint:processor"))
|
||||
|
||||
|
@ -1,46 +0,0 @@
|
||||
package com.m3u.data.extension
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import java.util.ServiceLoader
|
||||
|
||||
class RemoteServer : Service() {
|
||||
private val onRemoteServerCall: OnRemoteServerCall = ServiceLoader.load<OnRemoteServerCall>(
|
||||
OnRemoteServerCall::class.java
|
||||
).let {
|
||||
val count = it.count()
|
||||
if (count == 0) {
|
||||
throw IllegalStateException("No implementation of OnRemoteServerCall found")
|
||||
} else if (count > 1) {
|
||||
throw IllegalStateException("Multiple implementations of OnRemoteServerCall found")
|
||||
} else {
|
||||
it.first()
|
||||
}
|
||||
}
|
||||
private val binder: IRemoteService.Stub = object : IRemoteService.Stub() {
|
||||
override fun call(
|
||||
module: String,
|
||||
method: String,
|
||||
param: String,
|
||||
callback: IRemoteCallback?
|
||||
) {
|
||||
onRemoteServerCall.onCall(module, method, param, callback)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
Log.d(TAG, "onBind: $intent")
|
||||
return binder
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
Log.d(TAG, "onStartCommand: $intent, $flags, $startId")
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "RemoteClient"
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
package com.m3u.data.service
|
||||
|
||||
import android.util.Log
|
||||
import com.google.auto.service.AutoService
|
||||
import com.m3u.data.extension.IRemoteCallback
|
||||
import com.m3u.data.extension.OnRemoteServerCall
|
||||
import com.m3u.data.extension.RemoteCallException
|
||||
import java.util.ServiceLoader
|
||||
|
||||
@AutoService(OnRemoteServerCall::class)
|
||||
class OnRemoteServiceCallImpl : OnRemoteServerCall {
|
||||
private val modules = ServiceLoader.load(RemoteModule::class.java)
|
||||
?.toList().orEmpty().filterNotNull().associateBy { it.module }
|
||||
|
||||
override fun onCall(module: String, method: String, param: String, callback: IRemoteCallback?) {
|
||||
Log.d(TAG, "onCall: $module, $method, $param, $callback")
|
||||
try {
|
||||
val moduleInstance = modules[module]
|
||||
if (moduleInstance == null) {
|
||||
callback?.onError(
|
||||
module,
|
||||
method,
|
||||
OnRemoteServerCall.ERROR_CODE_MODULE_NOT_FOUNDED,
|
||||
"Module $module not founded"
|
||||
)
|
||||
return
|
||||
}
|
||||
if (method !in moduleInstance.methods) {
|
||||
callback?.onError(
|
||||
module,
|
||||
method,
|
||||
OnRemoteServerCall.ERROR_CODE_METHOD_NOT_FOUNDED,
|
||||
"Method $method not founded"
|
||||
)
|
||||
return
|
||||
}
|
||||
moduleInstance.callMethod(method, param, callback)
|
||||
} catch (e: RemoteCallException) {
|
||||
callback?.onError(
|
||||
module,
|
||||
method,
|
||||
e.errorCode,
|
||||
e.errorMessage
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
callback?.onError(
|
||||
module,
|
||||
method,
|
||||
OnRemoteServerCall.ERROR_CODE_UNCAUGHT,
|
||||
e.message
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "Host-OnRemoteServiceCallImpl"
|
||||
}
|
||||
}
|
||||
|
||||
interface RemoteModule {
|
||||
val module: String
|
||||
val methods: List<String>
|
||||
fun callMethod(method: String, param: String, callback: IRemoteCallback?)
|
||||
}
|
@ -5,7 +5,7 @@ plugins {
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.m3u.data.extension"
|
||||
namespace = "com.m3u.extension.api"
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package com.m3u.data.extension;
|
||||
|
||||
interface IRemoteCallback {
|
||||
void onSuccess(String module, String method, String param);
|
||||
void onSuccess(String module, String method, in byte[] param);
|
||||
void onError(String module, String method, int errorCode, String errorMessage);
|
||||
}
|
@ -3,5 +3,5 @@ package com.m3u.data.extension;
|
||||
import com.m3u.data.extension.IRemoteCallback;
|
||||
|
||||
interface IRemoteService {
|
||||
void call(String module, String method, String param, IRemoteCallback callback);
|
||||
void call(String module, String method, in byte[] param, IRemoteCallback callback);
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package com.m3u.data.extension
|
||||
|
||||
object Const {
|
||||
const val PACKAGE_NAME = "package_name"
|
||||
const val CLASS_NAME = "class_name"
|
||||
const val PERMISSION = "permission"
|
||||
const val ACCESS_KEY = "access_key"
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package com.m3u.data.extension
|
||||
|
||||
interface OnRemoteServerCall {
|
||||
fun onCall(module: String, method: String, param: String, callback: IRemoteCallback?)
|
||||
interface OnRemoteCall {
|
||||
operator fun invoke(module: String, method: String, bytes: ByteArray, callback: IRemoteCallback?)
|
||||
companion object {
|
||||
const val ERROR_CODE_MODULE_NOT_FOUNDED = -1
|
||||
const val ERROR_CODE_METHOD_NOT_FOUNDED = -2
|
||||
@ -12,4 +12,4 @@ interface OnRemoteServerCall {
|
||||
class RemoteCallException(
|
||||
val errorCode: Int,
|
||||
val errorMessage: String?
|
||||
): RuntimeException(errorMessage)
|
||||
) : RuntimeException(errorMessage)
|
@ -42,12 +42,16 @@ class RemoteClient {
|
||||
|
||||
fun connect(
|
||||
context: Context,
|
||||
targetPackageName: String = PACKAGE_NAME_HOST
|
||||
targetPackageName: String,
|
||||
targetClassName: String,
|
||||
targetPermission: String,
|
||||
accessKey: String
|
||||
) {
|
||||
Log.d(TAG, "connect")
|
||||
val intent = Intent(context, RemoteServer::class.java).apply {
|
||||
action = "com.m3u.permission.CONNECT_EXTENSION_PLUGIN"
|
||||
component = ComponentName(targetPackageName, "com.m3u.data.extension.RemoteServer")
|
||||
val intent = Intent(context, RemoteService::class.java).apply {
|
||||
action = targetPermission
|
||||
component = ComponentName(targetPackageName, targetClassName)
|
||||
putExtra(Const.ACCESS_KEY, accessKey)
|
||||
}
|
||||
context.bindService(intent, connection, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
@ -60,11 +64,11 @@ class RemoteClient {
|
||||
suspend fun call(
|
||||
module: String,
|
||||
method: String,
|
||||
param: String
|
||||
): String? = suspendCoroutine { cont ->
|
||||
param: ByteArray
|
||||
): ByteArray = suspendCoroutine { cont ->
|
||||
val remoteService = requireNotNull(server) { "RemoteService is not connected!" }
|
||||
remoteService.call(module, method, param, object : IRemoteCallback.Stub() {
|
||||
override fun onSuccess(module: String, method: String, param: String?) {
|
||||
override fun onSuccess(module: String, method: String, param: ByteArray) {
|
||||
Log.d(TAG, "onSuccess: $method, $param")
|
||||
cont.resume(param)
|
||||
}
|
||||
@ -89,6 +93,5 @@ class RemoteClient {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "RemoteClient"
|
||||
const val PACKAGE_NAME_HOST = "com.m3u.smartphone"
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package com.m3u.data.extension
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import java.util.ServiceLoader
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class RemoteService : Service() {
|
||||
private val onRemoteCall: OnRemoteCall by lazy {
|
||||
ServiceLoader.load<OnRemoteCall>(
|
||||
OnRemoteCall::class.java
|
||||
).let {
|
||||
val count = it.count()
|
||||
if (count == 0) {
|
||||
throw IllegalStateException("No implementation of OnRemoteCall found")
|
||||
} else if (count > 1) {
|
||||
throw IllegalStateException("Multiple implementations of OnRemoteCall found")
|
||||
} else {
|
||||
it.first()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val binders = ConcurrentHashMap<String, IRemoteService.Stub>()
|
||||
|
||||
private inner class RemoteServiceImpl: IRemoteService.Stub() {
|
||||
override fun call(
|
||||
module: String,
|
||||
method: String,
|
||||
param: ByteArray,
|
||||
callback: IRemoteCallback?
|
||||
) {
|
||||
onRemoteCall(module, method, param, callback)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
Log.d(TAG, "onBind: $intent")
|
||||
intent?: return null
|
||||
val packageName = intent.`package` ?: return null
|
||||
val binder = binders.getOrPut(packageName) {
|
||||
RemoteServiceImpl()
|
||||
}
|
||||
return binder
|
||||
}
|
||||
|
||||
override fun onUnbind(intent: Intent?): Boolean {
|
||||
Log.d(TAG, "onUnbind: $intent")
|
||||
intent ?: return super.onUnbind(intent)
|
||||
val packageName = intent.`package` ?: return super.onUnbind(intent)
|
||||
val binder = binders.remove(packageName)
|
||||
if (binder != null) {
|
||||
return true
|
||||
}
|
||||
return super.onUnbind(intent)
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
Log.d(TAG, "onStartCommand: $intent, $flags, $startId")
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "RemoteClient"
|
||||
}
|
||||
}
|
1
extension/runtime/.gitignore
vendored
Normal file
1
extension/runtime/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
38
extension/runtime/build.gradle.kts
Normal file
38
extension/runtime/build.gradle.kts
Normal file
@ -0,0 +1,38 @@
|
||||
plugins {
|
||||
alias(libs.plugins.com.android.library)
|
||||
alias(libs.plugins.org.jetbrains.kotlin.android)
|
||||
alias(libs.plugins.org.jetbrains.kotlin.serialization)
|
||||
alias(libs.plugins.com.google.devtools.ksp)
|
||||
id("com.squareup.wire")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.m3u.extension.runtime"
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
buildFeatures {
|
||||
aidl = true
|
||||
}
|
||||
}
|
||||
|
||||
wire {
|
||||
kotlin {}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":extension:api"))
|
||||
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.appcompat)
|
||||
|
||||
// reflect
|
||||
implementation(libs.org.jetbrains.kotlin.kotlin.reflect)
|
||||
|
||||
// auto
|
||||
implementation(libs.auto.service.annotations)
|
||||
ksp(libs.auto.service.ksp)
|
||||
|
||||
// wire
|
||||
implementation("com.squareup.wire:wire-runtime:4.9.2")
|
||||
}
|
0
extension/runtime/consumer-rules.pro
Normal file
0
extension/runtime/consumer-rules.pro
Normal file
21
extension/runtime/proguard-rules.pro
vendored
Normal file
21
extension/runtime/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
4
extension/runtime/src/main/AndroidManifest.xml
Normal file
4
extension/runtime/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
@ -0,0 +1,133 @@
|
||||
package com.m3u.extension.runtime
|
||||
|
||||
import android.util.Log
|
||||
import com.google.auto.service.AutoService
|
||||
import com.m3u.data.extension.IRemoteCallback
|
||||
import com.m3u.data.extension.OnRemoteCall
|
||||
import com.m3u.data.extension.RemoteCallException
|
||||
import com.squareup.wire.ProtoAdapter
|
||||
import java.lang.reflect.Method
|
||||
import java.lang.reflect.Parameter
|
||||
import java.util.ServiceLoader
|
||||
import kotlin.collections.orEmpty
|
||||
import kotlin.reflect.full.companionObject
|
||||
import kotlin.reflect.full.declaredMemberProperties
|
||||
|
||||
@AutoService(OnRemoteCall::class)
|
||||
class OnRemoteCallImpl : OnRemoteCall {
|
||||
private val remoteModules = ServiceLoader.load(RemoteModule::class.java)
|
||||
?.toList().orEmpty().filterNotNull().associateBy { it.module }
|
||||
|
||||
// Map<module-name, Map<method-name, method>>
|
||||
private val remoteMethods = mutableMapOf<String, Map<String, Method>>()
|
||||
|
||||
// Map<type-name, adapter>
|
||||
private val protobufAdapters = mutableMapOf<String, ProtoAdapter<*>>()
|
||||
|
||||
override fun invoke(
|
||||
module: String,
|
||||
method: String,
|
||||
bytes: ByteArray,
|
||||
callback: IRemoteCallback?
|
||||
) {
|
||||
Log.d(TAG, "$module, $method, ${bytes.size}, $callback")
|
||||
try {
|
||||
val instance = remoteModules[module]
|
||||
if (instance == null) {
|
||||
callback?.onError(
|
||||
module,
|
||||
method,
|
||||
OnRemoteCall.ERROR_CODE_MODULE_NOT_FOUNDED,
|
||||
"Module $module not founded"
|
||||
)
|
||||
return
|
||||
}
|
||||
invokeImpl(instance, module, method, bytes, callback)
|
||||
} catch (e: RemoteCallException) {
|
||||
callback?.onError(
|
||||
module,
|
||||
method,
|
||||
e.errorCode,
|
||||
e.errorMessage
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
callback?.onError(
|
||||
module,
|
||||
method,
|
||||
OnRemoteCall.ERROR_CODE_UNCAUGHT,
|
||||
e.message
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun invokeImpl(
|
||||
instance: RemoteModule,
|
||||
module: String,
|
||||
method: String,
|
||||
param: ByteArray,
|
||||
callback: IRemoteCallback?
|
||||
) {
|
||||
val methods = remoteMethods.getOrPut(module) {
|
||||
val moduleClass = instance::class.java
|
||||
moduleClass.declaredMethods
|
||||
.asSequence()
|
||||
.filter { it.isAnnotationPresent(RemoteMethod::class.java) }
|
||||
.filter { it.isAccessible }
|
||||
.toList()
|
||||
.associateBy { it.getAnnotation(RemoteMethod::class.java)!!.name }
|
||||
}
|
||||
val remoteMethod = methods[method]
|
||||
if (remoteMethod == null) {
|
||||
callback?.onError(
|
||||
module,
|
||||
method,
|
||||
OnRemoteCall.ERROR_CODE_METHOD_NOT_FOUNDED,
|
||||
"Method $method not founded"
|
||||
)
|
||||
return
|
||||
}
|
||||
// handle protobuf param
|
||||
val pbArg = remoteMethod.parameters
|
||||
.find { it.isAnnotationPresent(RemoteMethodParam::class.java) }
|
||||
?.let { decodeParamFromBytes(it, param) }
|
||||
|
||||
val args = listOfNotNull(
|
||||
pbArg,
|
||||
callback
|
||||
)
|
||||
|
||||
remoteMethod.invoke(instance, args)
|
||||
}
|
||||
|
||||
private fun decodeParamFromBytes(
|
||||
param: Parameter,
|
||||
bytes: ByteArray
|
||||
): Any? {
|
||||
val adapter = protobufAdapters.getOrPut(param.type.typeName) {
|
||||
val companionObject = Class.forName(param.type.typeName).kotlin.companionObject
|
||||
// TODO: fix this
|
||||
// Class.forName(param.type.typeName).kotlin.companionObjectInstance
|
||||
val adapter = companionObject?.declaredMemberProperties.orEmpty().find { it.name == "ADAPTER" }
|
||||
adapter as ProtoAdapter<*>
|
||||
}
|
||||
return adapter.decode(bytes)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "Host-OnRemoteCallImpl"
|
||||
}
|
||||
}
|
||||
|
||||
interface RemoteModule {
|
||||
val module: String
|
||||
}
|
||||
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
annotation class RemoteMethod(
|
||||
val name: String
|
||||
)
|
||||
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@Target(AnnotationTarget.VALUE_PARAMETER)
|
||||
annotation class RemoteMethodParam
|
@ -0,0 +1,25 @@
|
||||
package com.m3u.extension.runtime.business
|
||||
|
||||
import GetAppInfoRequest
|
||||
import GetAppInfoResponse
|
||||
import com.m3u.extension.runtime.RemoteMethod
|
||||
import com.m3u.extension.runtime.RemoteMethodParam
|
||||
import com.m3u.extension.runtime.RemoteModule
|
||||
|
||||
class InfoModule constructor(): RemoteModule {
|
||||
override val module: String = "info"
|
||||
|
||||
@RemoteMethod("getAppInfo")
|
||||
fun getAppInfo(
|
||||
@RemoteMethodParam param: GetAppInfoRequest,
|
||||
callback: (GetAppInfoResponse) -> Unit
|
||||
) {
|
||||
callback(
|
||||
GetAppInfoResponse(
|
||||
"com.m3u.extension.runtime",
|
||||
"InfoModule",
|
||||
"1.0.0"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
14
extension/runtime/src/main/proto/GetAppInfo.proto
Normal file
14
extension/runtime/src/main/proto/GetAppInfo.proto
Normal file
@ -0,0 +1,14 @@
|
||||
syntax = "proto3";
|
||||
|
||||
message GetAppInfoResponse {
|
||||
string app_id = 1;
|
||||
string app_version = 2;
|
||||
string app_name = 3;
|
||||
string app_icon = 4;
|
||||
string app_description = 5;
|
||||
string app_package_name = 6;
|
||||
}
|
||||
|
||||
message GetAppInfoRequest {
|
||||
|
||||
}
|
@ -173,13 +173,8 @@ androidx-graphics-shapes = { group = "androidx.graphics", name = "graphics-shape
|
||||
symbol-processing-api = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "kotlin-symbol-processor" }
|
||||
androidx-profileinstaller = { group = "androidx.profileinstaller", name = "profileinstaller", version.ref = "profileinstaller" }
|
||||
jakewharton-disklrucache = { group = "com.jakewharton", name = "disklrucache", version.ref = "jakewharton-disklrucache" }
|
||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
|
||||
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
|
||||
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
|
||||
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
||||
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
|
||||
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||
|
||||
org-jetbrains-kotlin-kotlin-reflect = { group = "org.jetbrains.kotlin", name = "kotlin-reflect", version.ref = "kotlin" }
|
||||
|
||||
[plugins]
|
||||
com-android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" }
|
||||
|
@ -15,7 +15,7 @@ dependencyResolutionManagement {
|
||||
}
|
||||
}
|
||||
rootProject.name = "M3U"
|
||||
include(":app:smartphone", ":app:tv")
|
||||
include(":app:smartphone", ":app:tv", ":app:extension")
|
||||
include(":core", ":core:foundation")
|
||||
include(":data")
|
||||
include(":data:codec", ":data:codec:lite", ":data:codec:rich")
|
||||
@ -33,5 +33,7 @@ include(
|
||||
":lint:annotation",
|
||||
":lint:processor"
|
||||
)
|
||||
include(":data:extension")
|
||||
include(":app:extension")
|
||||
include(
|
||||
":extension:api",
|
||||
":extension:runtime"
|
||||
)
|
||||
|
Reference in New Issue
Block a user