Skip to content

Commit

Permalink
Feat: Cp plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
zhufucdev committed Aug 14, 2023
1 parent 6fe90c6 commit 9698c39
Show file tree
Hide file tree
Showing 22 changed files with 715 additions and 10 deletions.
26 changes: 26 additions & 0 deletions .idea/androidTestResultsUserPreferences.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/appInsightsSettings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions .idea/deploymentTargetDropDown.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ dependencies {
implementation("com.madgag.spongycastle:bcpkix-jdk15on:1.58.0.0")
// AndroidX
implementation("androidx.core:core-ktx:${Versions.coreKtVersion}")
implementation("androidx.preference:preference-ktx:1.2.0")
implementation("androidx.preference:preference-ktx:1.2.1")
implementation("androidx.appcompat:appcompat:${Versions.appcompatVersion}")
implementation("com.google.android.material:material:${Versions.materialVersion}")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
Expand All @@ -95,7 +95,7 @@ dependencies {
implementation("androidx.navigation:navigation-dynamic-features-fragment:${Versions.navVersion}")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutineVersion}")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.kotlinxSerializationVersion}")
implementation("androidx.work:work-runtime-ktx:2.8.1")
implementation("androidx.work:work-runtime-ktx:${Versions.workRuntimeVersion}")
// Compose
implementation("androidx.lifecycle:lifecycle-runtime-ktx:${Versions.lifecycleRuntimeVersion}")
implementation("androidx.activity:activity-compose:${Versions.composeActivityVersion}")
Expand Down
5 changes: 3 additions & 2 deletions buildSrc/src/main/java/Versions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ object Versions {
const val appcompatVersion = "1.6.1"
const val coreKtVersion = "1.12.0-rc01"
const val coroutineVersion = "1.7.1"
const val composeUiVersion = "1.4.3"
const val composeUiVersion = "1.5.0"
const val composeCompilerVersion = "1.5.0"
const val composeActivityVersion = "1.7.2"
const val composeMaterialVersion = "1.2.0-alpha04"
const val ktorVersion = "2.3.2"
const val kotlinVersion = "1.9.0"
const val kotlinxSerializationVersion = "1.5.1"
const val lifecycleRuntimeVersion = "2.6.1"
const val navVersion = "2.6.0"
const val navVersion = "2.7.0"
const val jnanoidVersion = "2.0.0"
const val materialVersion = "1.9.0"
const val workRuntimeVersion = "2.8.1"
const val yukiVersion = "1.1.11"
}
5 changes: 4 additions & 1 deletion cp_plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,17 @@ android {

dependencies {
implementation("androidx.core:core-ktx:${Versions.coreKtVersion}")
implementation(project(":stub_plugin_xposed"))
implementation("io.ktor:ktor-client-serialization-jvm:${Versions.ktorVersion}")
implementation("org.jetbrains.kotlin:kotlin-reflect:${Versions.kotlinVersion}")
implementation("androidx.work:work-runtime-ktx:${Versions.workRuntimeVersion}")
// Xposed
ksp("com.highcapable.yukihookapi:ksp-xposed:${Versions.yukiVersion}")
implementation("com.highcapable.yukihookapi:api:${Versions.yukiVersion}")
compileOnly("de.robv.android.xposed:api:82")
// Internal
implementation(project(":stub"))
implementation(project(":stub_plugin"))
implementation(project(":stub_plugin_xposed"))

testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.zhufucdev.cp_plugin

import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.zhufucdev.cp_plugin.provider.bridge
import com.zhufucdev.stub.EmulationInfo
import com.zhufucdev.stub_plugin.MePlugin
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

import org.junit.Test
import org.junit.runner.RunWith

import org.junit.Assert.*
import kotlin.time.Duration.Companion.seconds

/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ContentProviderInstrumentedTest {
@Test
fun useSelfhosted() {
val context = InstrumentationRegistry.getInstrumentation().context
val scope = CoroutineScope(Dispatchers.Default)

scope.launch {
val server = MePlugin.queryServer(context)
server.bridge()
}

useConnect()
}

@Test
fun useConnect() {
val cr = InstrumentationRegistry.getInstrumentation().context.contentResolver
runBlocking {
delay(1.seconds)
ContentProviderServer(PROVIDER_AUTHORITY, cr).connect("whatever") {
sendStarted(EmulationInfo(10.0, 10.0, BuildConfig.APPLICATION_ID))
}
}
}
}
18 changes: 18 additions & 0 deletions cp_plugin/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<application
android:allowBackup="true"
android:label="@string/app_name"
android:name=".Application"
android:icon="@mipmap/ic_launcher"
android:supportsRtl="true">

<receiver
android:name=".ControllerReceiver"
android:exported="true" />

<provider
android:authorities="com.zhufucdev.motion_emulator.event_provider"
android:name=".provider.EventProvider"
android:exported="true" />

<service
android:name=".provider.EmulationBridgeService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="specialUse"
android:permission="android.permission.BIND_JOB_SERVICE" />

<meta-data
android:name="me_description"
android:value="@string/text_description" />
Expand Down
5 changes: 5 additions & 0 deletions cp_plugin/src/main/java/com/zhufucdev/cp_plugin/Constants.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.zhufucdev.cp_plugin

const val SHARED_PREFS_NAME = "cp_plugin_prefs"

const val PROVIDER_AUTHORITY = "com.zhufucdev.motion_emulator.event_provider"
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package com.zhufucdev.cp_plugin

import android.annotation.SuppressLint
import android.content.ContentResolver
import android.content.ContentValues
import android.database.Cursor
import android.net.Uri
import androidx.core.content.contentValuesOf
import com.zhufucdev.stub.Box
import com.zhufucdev.stub.Emulation
import com.zhufucdev.stub.EmulationInfo
import com.zhufucdev.stub.Intermediate
import com.zhufucdev.stub_plugin.ServerConnection
import com.zhufucdev.stub_plugin.ServerScope
import kotlinx.serialization.json.Json
import java.util.Optional
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext

const val EMULATION_START = 0x00
const val EMULATION_STOP = 0x01

data class ContentProviderServer(val authority: String, val resolver: ContentResolver)

@SuppressLint("Recycle")
suspend fun ContentProviderServer.connect(
id: String,
block: suspend ServerScope.() -> Unit
): ServerConnection {
val emulation =
runCatching {
resolver
.query(Uri.parse("content://$authority/next"), null, id, null, null)
.use(::parseEmulation)
}.getOrNull()
val context = coroutineContext
if (emulation == null) {
val scope = object : ServerScope {
override val emulation: Optional<Emulation> = Optional.empty()

override val coroutineContext: CoroutineContext = context

override suspend fun sendStarted(info: EmulationInfo) {
throw NotImplementedError()
}

override suspend fun sendProgress(intermediate: Intermediate) {
throw NotImplementedError()
}

override suspend fun close() {
throw NotImplementedError()
}
}
block(scope)
return object : ServerConnection {
override val successful: Boolean = false

override fun close() {
// nothing
}
}
} else {
val scope = object : ServerScope {
override val emulation: Optional<Emulation> = emulation

override val coroutineContext: CoroutineContext = context

override suspend fun sendStarted(info: EmulationInfo) {
val uri = Uri.parse("content://$authority/state")
resolver.update(uri, info.contentValues(), id, null)
}

override suspend fun sendProgress(intermediate: Intermediate) {
val uri = Uri.parse("content://$authority/progress")
resolver.update(uri, intermediate.contentValues(), id, null)
}

override suspend fun close() {
// does nothing
}
}
block(scope)
resolver.sendStop(authority, id)
return object : ServerConnection {
override val successful: Boolean = false

override fun close() {
// nothing
}
}
}
}

private fun parseEmulation(cursor: Cursor?): Optional<Emulation>? {
if (cursor == null) {
return null
}
cursor.moveToFirst()
if (cursor.getInt(0) == EMULATION_STOP) {
return Optional.empty()
}
val traceData = cursor.getString(1)
val motionData = cursor.getString(2)
val cellsData = cursor.getString(3)
val velocity = cursor.getDouble(4)
val repeat = cursor.getInt(5)
val satellites = cursor.getInt(6)
return Optional.of(
Emulation(
Json.decodeFromString(traceData),
Box.decodeFromString(motionData),
Box.decodeFromString(cellsData),
velocity, repeat, satellites
)
)
}

private fun EmulationInfo.contentValues(): ContentValues {
return contentValuesOf(
"running" to true,
"duration" to duration,
"length" to length,
"owner" to owner
)
}

private fun Intermediate.contentValues(): ContentValues {
return contentValuesOf(
"progress" to progress,
"coord_sys" to location.coordinateSystem.ordinal,
"pos_la" to location.latitude,
"pos_lg" to location.longitude,
"elapsed" to elapsed
)
}

private fun ContentResolver.sendStop(authority: String, id: String) {
update(Uri.parse("content://$authority/state"), contentValuesOf("running" to false), id, null)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.zhufucdev.cp_plugin

import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.highcapable.yukihookapi.hook.factory.prefs
import com.zhufucdev.cp_plugin.provider.EmulationBridgeService
import com.zhufucdev.stub_plugin.MePlugin
import com.zhufucdev.stub_plugin.PluginBroadcastReceiver

class ControllerReceiver : PluginBroadcastReceiver() {
override fun onEmulationStart(context: Context) {
context.prefs(SHARED_PREFS_NAME).edit {
putInt("method", MePlugin.queryMethod(context).ordinal)
}

// Intent(context, EmulationBridgeService::class.java).apply {
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// context.startForegroundService(this)
// } else {
// context.startService(this)
// }
// }
}

override fun onEmulationStop(context: Context) {
// Intent(context, EmulationBridgeService::class.java).apply {
// context.stopService(this)
// }
}
}
Loading

0 comments on commit 9698c39

Please sign in to comment.