Skip to content

Commit

Permalink
feat(pasteboard): support android (#362)
Browse files Browse the repository at this point in the history
* feat(pasteboard): support android

* fix: handle some case

* fix: add provider manifest

* fix: wrong uri

* revert: revert not handle content uri
  • Loading branch information
Seidko authored Jan 5, 2025
1 parent c628747 commit 611bdda
Show file tree
Hide file tree
Showing 11 changed files with 221 additions and 3 deletions.
9 changes: 9 additions & 0 deletions packages/pasteboard/android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.cxx
68 changes: 68 additions & 0 deletions packages/pasteboard/android/build.gradle
Original file line number Diff line number Diff line change
@@ -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
}
}
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions packages/pasteboard/android/settings.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rootProject.name = 'pasteboard'
4 changes: 4 additions & 0 deletions packages/pasteboard/android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="one.mixin.pasteboard">

</manifest>
Original file line number Diff line number Diff line change
@@ -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<String> = 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<List<String>>() ?: 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<ByteArray>() ?: 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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="external_files"
path="." />
</paths>
6 changes: 3 additions & 3 deletions packages/pasteboard/lib/src/pasteboard_platform_io.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class PasteboardPlatformIO implements PasteboardPlatform {

@override
Future<String?> get html async {
if (Platform.isWindows) {
if (Platform.isWindows || Platform.isAndroid) {
return await _channel.invokeMethod<Object>('html') as String?;
}
return null;
Expand All @@ -35,7 +35,7 @@ class PasteboardPlatformIO implements PasteboardPlatform {
if (image == null) {
return null;
}
if (Platform.isMacOS || Platform.isLinux || Platform.isIOS) {
if (Platform.isMacOS || Platform.isLinux || Platform.isIOS || Platform.isAndroid) {
return image as Uint8List;
} else if (Platform.isWindows) {
final file = File(image as String);
Expand All @@ -62,7 +62,7 @@ class PasteboardPlatformIO implements PasteboardPlatform {
if (image == null) {
return;
}
if (Platform.isIOS || Platform.isMacOS) {
if (Platform.isIOS || Platform.isMacOS || Platform.isAndroid) {
await _channel.invokeMethod<void>('writeImage', image);
} else if (Platform.isWindows) {
final file = await File(GetTempFileName()).create();
Expand Down
3 changes: 3 additions & 0 deletions packages/pasteboard/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ dev_dependencies:
flutter:
plugin:
platforms:
android:
package: one.mixin.pasteboard
pluginClass: PasteboardPlugin
macos:
pluginClass: PasteboardPlugin
windows:
Expand Down

0 comments on commit 611bdda

Please sign in to comment.