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:
@ -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
data/extension/.gitignore
vendored
1
data/extension/.gitignore
vendored
@ -1 +0,0 @@
|
||||
/build
|
@ -1,20 +0,0 @@
|
||||
plugins {
|
||||
alias(libs.plugins.com.android.library)
|
||||
alias(libs.plugins.org.jetbrains.kotlin.android)
|
||||
alias(libs.plugins.org.jetbrains.kotlin.serialization)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.m3u.data.extension"
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
buildFeatures {
|
||||
aidl = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.appcompat)
|
||||
}
|
21
data/extension/proguard-rules.pro
vendored
21
data/extension/proguard-rules.pro
vendored
@ -1,21 +0,0 @@
|
||||
# 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
|
@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
@ -1,6 +0,0 @@
|
||||
package com.m3u.data.extension;
|
||||
|
||||
interface IRemoteCallback {
|
||||
void onSuccess(String module, String method, String param);
|
||||
void onError(String module, String method, int errorCode, String errorMessage);
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package com.m3u.data.extension;
|
||||
|
||||
import com.m3u.data.extension.IRemoteCallback;
|
||||
|
||||
interface IRemoteService {
|
||||
void call(String module, String method, String param, IRemoteCallback callback);
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
package com.m3u.data.extension
|
||||
|
||||
interface OnRemoteServerCall {
|
||||
fun onCall(module: String, method: String, param: String, callback: IRemoteCallback?)
|
||||
companion object {
|
||||
const val ERROR_CODE_MODULE_NOT_FOUNDED = -1
|
||||
const val ERROR_CODE_METHOD_NOT_FOUNDED = -2
|
||||
const val ERROR_CODE_UNCAUGHT = -3
|
||||
}
|
||||
}
|
||||
|
||||
class RemoteCallException(
|
||||
val errorCode: Int,
|
||||
val errorMessage: String?
|
||||
): RuntimeException(errorMessage)
|
@ -1,94 +0,0 @@
|
||||
package com.m3u.data.extension
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
class RemoteClient {
|
||||
private var server: IRemoteService? = null
|
||||
private val connection = object : ServiceConnection {
|
||||
override fun onServiceConnected(
|
||||
name: ComponentName?,
|
||||
service: IBinder?
|
||||
) {
|
||||
server = IRemoteService.Stub.asInterface(service)
|
||||
_isConnectedObservable.value = true
|
||||
Log.d(TAG, "onServiceConnected, $name")
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
server = null
|
||||
_isConnectedObservable.value = false
|
||||
Log.d(TAG, "onServiceDisconnected, $name")
|
||||
}
|
||||
|
||||
override fun onBindingDied(name: ComponentName?) {
|
||||
super.onBindingDied(name)
|
||||
Log.e(TAG, "onBindingDied: $name")
|
||||
}
|
||||
|
||||
override fun onNullBinding(name: ComponentName?) {
|
||||
super.onNullBinding(name)
|
||||
Log.e(TAG, "onNullBinding: $name")
|
||||
}
|
||||
}
|
||||
|
||||
fun connect(
|
||||
context: Context,
|
||||
targetPackageName: String = PACKAGE_NAME_HOST
|
||||
) {
|
||||
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")
|
||||
}
|
||||
context.bindService(intent, connection, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
|
||||
fun disconnect(context: Context) {
|
||||
context.unbindService(connection)
|
||||
_isConnectedObservable.value = false
|
||||
}
|
||||
|
||||
suspend fun call(
|
||||
module: String,
|
||||
method: String,
|
||||
param: String
|
||||
): String? = 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?) {
|
||||
Log.d(TAG, "onSuccess: $method, $param")
|
||||
cont.resume(param)
|
||||
}
|
||||
|
||||
override fun onError(
|
||||
module: String,
|
||||
method: String,
|
||||
errorCode: Int,
|
||||
errorMessage: String?
|
||||
) {
|
||||
Log.e(TAG, "onError: $method, $errorCode, $errorMessage")
|
||||
throw RuntimeException("Error: $method $param $errorCode, $errorMessage")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
val isConnected: Boolean
|
||||
get() = server != null
|
||||
|
||||
val isConnectedObservable: Flow<Boolean> get() = _isConnectedObservable
|
||||
private val _isConnectedObservable = MutableStateFlow(false)
|
||||
|
||||
companion object {
|
||||
private const val TAG = "RemoteClient"
|
||||
const val PACKAGE_NAME_HOST = "com.m3u.smartphone"
|
||||
}
|
||||
}
|
@ -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?)
|
||||
}
|
Reference in New Issue
Block a user