diff --git a/packages/pasteboard/android/.gitignore b/packages/pasteboard/android/.gitignore
new file mode 100644
index 00000000..161bdcda
--- /dev/null
+++ b/packages/pasteboard/android/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.cxx
diff --git a/packages/pasteboard/android/build.gradle b/packages/pasteboard/android/build.gradle
new file mode 100644
index 00000000..2376a19f
--- /dev/null
+++ b/packages/pasteboard/android/build.gradle
@@ -0,0 +1,68 @@
+group = "one.mixin.pasteboard"
+version = "1.0-SNAPSHOT"
+
+buildscript {
+ ext.kotlin_version = "1.8.22"
+ repositories {
+ google()
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath("com.android.tools.build:gradle:8.1.4")
+ classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+apply plugin: "com.android.library"
+apply plugin: "kotlin-android"
+
+android {
+ if (project.android.hasProperty("namespace")) {
+ namespace = "one.mixin.pasteboard"
+ }
+
+ compileSdk = 34
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_1_8
+ }
+
+ sourceSets {
+ main.java.srcDirs += "src/main/kotlin"
+ test.java.srcDirs += "src/test/kotlin"
+ }
+
+ defaultConfig {
+ minSdk = 21
+ }
+
+ dependencies {
+ testImplementation("org.jetbrains.kotlin:kotlin-test")
+ testImplementation("org.mockito:mockito-core:5.0.0")
+ }
+
+ testOptions {
+ unitTests.all {
+ useJUnitPlatform()
+
+ testLogging {
+ events "passed", "skipped", "failed", "standardOut", "standardError"
+ outputs.upToDateWhen {false}
+ showStandardStreams = true
+ }
+ }
+ }
+}
diff --git a/packages/pasteboard/android/gradle/wrapper/gradle-wrapper.jar b/packages/pasteboard/android/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..d64cd491
Binary files /dev/null and b/packages/pasteboard/android/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/packages/pasteboard/android/gradle/wrapper/gradle-wrapper.properties b/packages/pasteboard/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..1af9e093
--- /dev/null
+++ b/packages/pasteboard/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/packages/pasteboard/android/settings.gradle b/packages/pasteboard/android/settings.gradle
new file mode 100644
index 00000000..2a9683e3
--- /dev/null
+++ b/packages/pasteboard/android/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'pasteboard'
diff --git a/packages/pasteboard/android/src/main/AndroidManifest.xml b/packages/pasteboard/android/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..39e27b24
--- /dev/null
+++ b/packages/pasteboard/android/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/packages/pasteboard/android/src/main/kotlin/one/mixin/pasteboard/PasteboardPlugin.kt b/packages/pasteboard/android/src/main/kotlin/one/mixin/pasteboard/PasteboardPlugin.kt
new file mode 100644
index 00000000..ca3e7158
--- /dev/null
+++ b/packages/pasteboard/android/src/main/kotlin/one/mixin/pasteboard/PasteboardPlugin.kt
@@ -0,0 +1,111 @@
+package one.mixin.pasteboard
+
+
+import android.content.ClipData
+import android.content.ClipboardManager
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.net.Uri
+import androidx.core.content.FileProvider
+import io.flutter.embedding.engine.plugins.FlutterPlugin
+import io.flutter.plugin.common.MethodCall
+import io.flutter.plugin.common.MethodChannel
+import io.flutter.plugin.common.MethodChannel.MethodCallHandler
+import io.flutter.plugin.common.MethodChannel.Result
+import java.io.ByteArrayOutputStream
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import java.io.IOException
+import java.util.UUID
+import kotlin.concurrent.thread
+
+/** PasteboardPlugin */
+class PasteboardPlugin: FlutterPlugin, MethodCallHandler {
+ /// The MethodChannel that will the communication between Flutter and native Android
+ ///
+ /// This local reference serves to register the plugin with the Flutter Engine and unregister it
+ /// when the Flutter Engine is detached from the Activity
+ private lateinit var context: Context
+ private lateinit var channel : MethodChannel
+
+ override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
+ channel = MethodChannel(flutterPluginBinding.binaryMessenger, "pasteboard")
+ channel.setMethodCallHandler(this)
+ context = flutterPluginBinding.applicationContext
+ }
+
+ override fun onMethodCall(call: MethodCall, result: Result) {
+ val manager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
+ val cr = context.contentResolver
+ val first = manager.primaryClip?.getItemAt(0)
+ when (call.method) {
+ "image" -> {
+ first?.uri?.let {
+ val mime = cr.getType(it)
+ if (mime == null || !mime.startsWith("image")) return result.success(null)
+ result.success(cr.openInputStream(it).use { stream ->
+ stream?.buffered()?.readBytes()
+ })
+ }
+ result.success(null)
+ }
+ "files" -> {
+ manager.primaryClip?.run {
+ if (itemCount == 0) result.success(null)
+ val files: MutableList = mutableListOf()
+ for (i in 0 until itemCount) {
+ getItemAt(i).uri?.let {
+ files.add(it.toString())
+ }
+ }
+ result.success(files)
+ }
+ }
+ "html" -> result.success(first?.htmlText)
+ "writeFiles" -> {
+ val args = call.arguments>() ?: return result.error(
+ "NoArgs",
+ "Missing Arguments",
+ null,
+ )
+ val clip: ClipData? = null
+ for (i in args) {
+ val uri = Uri.parse(i)
+ clip ?: ClipData.newUri(cr, "files", uri)
+ clip?.addItem(ClipData.Item(uri))
+ }
+ clip?.let {
+ manager.setPrimaryClip(it)
+ }
+ result.success(null)
+ }
+ "writeImage" -> {
+ val image = call.arguments() ?: return result.error(
+ "NoArgs",
+ "Missing Arguments",
+ null,
+ )
+ val out = ByteArrayOutputStream()
+ thread {
+ val bitmap = BitmapFactory.decodeByteArray(image, 0, image.size)
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
+ }
+ val name = UUID.randomUUID().toString()
+ val file = File(context.cacheDir, name)
+ FileOutputStream(file).use {
+ out.writeTo(it)
+ }
+ val uri = FileProvider.getUriForFile(context, "${context.packageName}.provider", file)
+ val clip = ClipData.newUri(cr, "image.png", uri)
+ manager.setPrimaryClip(clip)
+ }
+ else -> result.notImplemented()
+ }
+ }
+
+ override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
+ channel.setMethodCallHandler(null)
+ }
+}
diff --git a/packages/pasteboard/example/android/app/src/main/AndroidManifest.xml b/packages/pasteboard/example/android/app/src/main/AndroidManifest.xml
index 74a78b93..69310c00 100644
--- a/packages/pasteboard/example/android/app/src/main/AndroidManifest.xml
+++ b/packages/pasteboard/example/android/app/src/main/AndroidManifest.xml
@@ -30,6 +30,15 @@
+
+
+