[jnigen] Android plugin example (https://github.com/dart-lang/jnigen/issues/46)
diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml index 0b4123f..9bae125 100644 --- a/.github/workflows/test-package.yml +++ b/.github/workflows/test-package.yml
@@ -212,6 +212,33 @@ - run: flutter pub get - run: flutter build apk + build_notification_plugin_example: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./pkgs/jni_gen/examples/notification_plugin + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-java@v2 + with: + distribution: 'zulu' + java-version: '11' + - uses: subosito/flutter-action@v2 + with: + channel: 'stable' + cache: true + cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' + - run: flutter pub get + - run: flutter analyze + - run: flutter build apk + working-directory: ./pkgs/jni_gen/examples/notification_plugin/example + - name: re-generate bindings + run: flutter pub run jni_gen -Ddart_root=_dart -Dc_root=_c --config jnigen.yaml + - name: compare generated dart bindings + run: diff -qr lib/ _dart + - name: compare generated C bindings + run: diff -qr src/ _c + build_in_app_java_example: runs-on: ubuntu-latest defaults:
diff --git a/pkgs/jni_gen/examples/in_app_java/android/app/build.gradle b/pkgs/jni_gen/examples/in_app_java/android/app/build.gradle index 784c659..bd28789 100644 --- a/pkgs/jni_gen/examples/in_app_java/android/app/build.gradle +++ b/pkgs/jni_gen/examples/in_app_java/android/app/build.gradle
@@ -61,7 +61,7 @@ } } -externalNativeBuild { + externalNativeBuild { cmake { path "../../src/android_utils/CMakeLists.txt" }
diff --git a/pkgs/jni_gen/examples/in_app_java/android/app/src/main/java/com/example/in_app_java/AndroidUtils.java b/pkgs/jni_gen/examples/in_app_java/android/app/src/main/java/com/example/in_app_java/AndroidUtils.java index 4c297bf..a998516 100644 --- a/pkgs/jni_gen/examples/in_app_java/android/app/src/main/java/com/example/in_app_java/AndroidUtils.java +++ b/pkgs/jni_gen/examples/in_app_java/android/app/src/main/java/com/example/in_app_java/AndroidUtils.java
@@ -1,3 +1,7 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + package com.example.in_app_java; import android.app.Activity;
diff --git a/pkgs/jni_gen/examples/in_app_java/lib/main.dart b/pkgs/jni_gen/examples/in_app_java/lib/main.dart index b47cb31..10081f5 100644 --- a/pkgs/jni_gen/examples/in_app_java/lib/main.dart +++ b/pkgs/jni_gen/examples/in_app_java/lib/main.dart
@@ -1,3 +1,7 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + import 'package:flutter/material.dart'; import 'package:jni/jni.dart';
diff --git a/pkgs/jni_gen/examples/in_app_java/test/widget_test.dart b/pkgs/jni_gen/examples/in_app_java/test/widget_test.dart deleted file mode 100644 index 0c153cc..0000000 --- a/pkgs/jni_gen/examples/in_app_java/test/widget_test.dart +++ /dev/null
@@ -1,30 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:in_app_java/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -}
diff --git a/pkgs/jni_gen/examples/in_app_java/tool/generate_bindings.dart b/pkgs/jni_gen/examples/in_app_java/tool/generate_bindings.dart index 2628d99..cb2b53d 100644 --- a/pkgs/jni_gen/examples/in_app_java/tool/generate_bindings.dart +++ b/pkgs/jni_gen/examples/in_app_java/tool/generate_bindings.dart
@@ -1,3 +1,7 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + import 'package:jni_gen/jni_gen.dart'; void main(List<String> args) async { @@ -8,7 +12,7 @@ dartRoot: Uri.directory('lib/android_utils'), libraryName: 'android_utils', androidSdkConfig: AndroidSdkConfig( - versions: [31], + addGradleDeps: true, ), ); await generateJniBindings(config);
diff --git a/pkgs/jni_gen/examples/notification_plugin/.gitignore b/pkgs/jni_gen/examples/notification_plugin/.gitignore new file mode 100644 index 0000000..96486fd --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/.gitignore
@@ -0,0 +1,30 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/
diff --git a/pkgs/jni_gen/examples/notification_plugin/.metadata b/pkgs/jni_gen/examples/notification_plugin/.metadata new file mode 100644 index 0000000..d677252 --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/.metadata
@@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: ffccd96b62ee8cec7740dab303538c5fc26ac543 + channel: stable + +project_type: plugin_ffi + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: ffccd96b62ee8cec7740dab303538c5fc26ac543 + base_revision: ffccd96b62ee8cec7740dab303538c5fc26ac543 + - platform: android + create_revision: ffccd96b62ee8cec7740dab303538c5fc26ac543 + base_revision: ffccd96b62ee8cec7740dab303538c5fc26ac543 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/pkgs/jni_gen/examples/notification_plugin/README.md b/pkgs/jni_gen/examples/notification_plugin/README.md new file mode 100644 index 0000000..1fcccdc --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/README.md
@@ -0,0 +1,13 @@ +# notification_plugin + +Example of android plugin project with jni_gen. + +This plugin project contains [custom code](android/src/main/java/com/example/notification_plugin) which uses the Android libraries. The bindings are generated using [jnigen config](jnigen.yaml) and then used in [flutter example](example/lib/main.dart), with help of `package:jni` APIs. + +The command to regenerate JNI bindings is: +``` +flutter run jni_gen --config jnigen.yaml # run from notification_plugin project root +``` + +The `example/` app must be built at least once in _release_ mode (eg `flutter build apk`) before running jni_gen. This is the equivalent of Gradle Sync in Android Studio, and enables `jni_gen` to run a Gradle stub and determine release build's classpath, which contains the paths to relevant dependencies. Therefore a build must have been run after cleaning build directories, or updating Java dependencies. This is a known complexity of the Gradle build system, and if you know a solution, please contribute to issue discussion at #33. +
diff --git a/pkgs/jni_gen/examples/notification_plugin/analysis_options.yaml b/pkgs/jni_gen/examples/notification_plugin/analysis_options.yaml new file mode 100644 index 0000000..a5744c1 --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/analysis_options.yaml
@@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options
diff --git a/pkgs/jni_gen/examples/notification_plugin/android/.gitignore b/pkgs/jni_gen/examples/notification_plugin/android/.gitignore new file mode 100644 index 0000000..161bdcd --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/android/.gitignore
@@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.cxx
diff --git a/pkgs/jni_gen/examples/notification_plugin/android/build.gradle b/pkgs/jni_gen/examples/notification_plugin/android/build.gradle new file mode 100644 index 0000000..7643ec5 --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/android/build.gradle
@@ -0,0 +1,59 @@ +// The Android Gradle Plugin builds the native code with the Android NDK. + +group 'com.example.notification_plugin' +version '1.0' + +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + // The Android Gradle Plugin knows how to build native code with the NDK. + classpath 'com.android.tools.build:gradle:7.1.2' + } +} + +rootProject.allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' + +android { + // Bumping the plugin compileSdkVersion requires all clients of this plugin + // to bump the version in their app. + compileSdkVersion 31 + + // Bumping the plugin ndkVersion requires all clients of this plugin to bump + // the version in their app and to download a newer version of the NDK. + ndkVersion "21.1.6352462" + + // Invoke the shared CMake build with the Android Gradle Plugin. + externalNativeBuild { + cmake { + path "../src/CMakeLists.txt" + + // The default CMake version for the Android Gradle Plugin is 3.10.2. + // https://developer.android.com/studio/projects/install-ndk#vanilla_cmake + // + // The Flutter tooling requires that developers have CMake 3.10 or later + // installed. You should not increase this version, as doing so will cause + // the plugin to fail to compile for some customers of the plugin. + // version "3.10.2" + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + minSdkVersion 16 + } +}
diff --git a/pkgs/jni_gen/examples/notification_plugin/android/settings.gradle b/pkgs/jni_gen/examples/notification_plugin/android/settings.gradle new file mode 100644 index 0000000..8e0a5b1 --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/android/settings.gradle
@@ -0,0 +1 @@ +rootProject.name = 'notification_plugin'
diff --git a/pkgs/jni_gen/examples/notification_plugin/android/src/main/AndroidManifest.xml b/pkgs/jni_gen/examples/notification_plugin/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..01cb165 --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/android/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.example.notification_plugin"> +</manifest>
diff --git a/pkgs/jni_gen/examples/notification_plugin/android/src/main/java/com/example/notification_plugin/Notifications.java b/pkgs/jni_gen/examples/notification_plugin/android/src/main/java/com/example/notification_plugin/Notifications.java new file mode 100644 index 0000000..59f8b7a --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/android/src/main/java/com/example/notification_plugin/Notifications.java
@@ -0,0 +1,36 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.example.notification_plugin; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.os.Build; +import androidx.annotation.Keep; +import androidx.core.app.NotificationCompat; + +@Keep +public class Notifications { + public static void showNotification( + Context context, int notificationID, String title, String text) { + @SuppressWarnings("deprecation") + NotificationCompat.Builder builder = + new NotificationCompat.Builder(context) + .setSmallIcon(android.R.drawable.ic_dialog_info) + .setContentTitle(title) + .setContentText(text); + NotificationManager notificationManager = + (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) { + String channelId = "my_channel"; + NotificationChannel channel = + new NotificationChannel( + channelId, "My App's notifications", NotificationManager.IMPORTANCE_HIGH); + notificationManager.createNotificationChannel(channel); + builder.setChannelId(channelId); + } + notificationManager.notify(notificationID, builder.build()); + } +}
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/.gitignore b/pkgs/jni_gen/examples/notification_plugin/example/.gitignore new file mode 100644 index 0000000..24476c5 --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/example/.gitignore
@@ -0,0 +1,44 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/README.md b/pkgs/jni_gen/examples/notification_plugin/example/README.md new file mode 100644 index 0000000..0ec9e1d --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/example/README.md
@@ -0,0 +1,4 @@ +# notification_plugin_example + +This app demonstrates how to use the notification_plugin plugin. +
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/analysis_options.yaml b/pkgs/jni_gen/examples/notification_plugin/example/analysis_options.yaml new file mode 100644 index 0000000..61b6c4d --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/example/analysis_options.yaml
@@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/.gitignore b/pkgs/jni_gen/examples/notification_plugin/example/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/example/android/.gitignore
@@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/build.gradle b/pkgs/jni_gen/examples/notification_plugin/example/android/app/build.gradle new file mode 100644 index 0000000..ee3c62f --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/build.gradle
@@ -0,0 +1,71 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.notification_plugin_example" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +}
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/debug/AndroidManifest.xml b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..822a208 --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,8 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.example.notification_plugin_example"> + <!-- The INTERNET permission is required for development. Specifically, + the Flutter tool needs it to communicate with the running application + to allow setting breakpoints, to provide hot reload, etc. + --> + <uses-permission android:name="android.permission.INTERNET"/> +</manifest>
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/AndroidManifest.xml b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..60e7eab --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,34 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.example.notification_plugin_example"> + <application + android:label="notification_plugin_example" + android:name="${applicationName}" + android:icon="@mipmap/ic_launcher"> + <activity + android:name=".MainActivity" + android:exported="true" + android:launchMode="singleTop" + android:theme="@style/LaunchTheme" + android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" + android:hardwareAccelerated="true" + android:windowSoftInputMode="adjustResize"> + <!-- Specifies an Android theme to apply to this Activity as soon as + the Android process has started. This theme is visible to the user + while the Flutter UI initializes. After that, this theme continues + to determine the Window background behind the Flutter UI. --> + <meta-data + android:name="io.flutter.embedding.android.NormalTheme" + android:resource="@style/NormalTheme" + /> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <!-- Don't delete the meta-data below. + This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> + <meta-data + android:name="flutterEmbedding" + android:value="2" /> + </application> +</manifest>
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/kotlin/com/example/notification_plugin_example/MainActivity.kt b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/kotlin/com/example/notification_plugin_example/MainActivity.kt new file mode 100644 index 0000000..af88e15 --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/kotlin/com/example/notification_plugin_example/MainActivity.kt
@@ -0,0 +1,6 @@ +package com.example.notification_plugin_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +}
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/drawable-v21/launch_background.xml b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Modify this file to customize your launch splash screen --> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="?android:colorBackground" /> + + <!-- You can insert your own image assets here --> + <!-- <item> + <bitmap + android:gravity="center" + android:src="@mipmap/launch_image" /> + </item> --> +</layer-list>
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/drawable/launch_background.xml b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Modify this file to customize your launch splash screen --> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@android:color/white" /> + + <!-- You can insert your own image assets here --> + <!-- <item> + <bitmap + android:gravity="center" + android:src="@mipmap/launch_image" /> + </item> --> +</layer-list>
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png Binary files differ
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png Binary files differ
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png Binary files differ
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png Binary files differ
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png Binary files differ
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/values-night/styles.xml b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on --> + <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar"> + <!-- Show a splash screen on the activity. Automatically removed when + the Flutter engine draws its first frame --> + <item name="android:windowBackground">@drawable/launch_background</item> + </style> + <!-- Theme applied to the Android Window as soon as the process has started. + This theme determines the color of the Android Window while your + Flutter UI initializes, as well as behind your Flutter UI while its + running. + + This Theme is only used starting with V2 of Flutter's Android embedding. --> + <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar"> + <item name="android:windowBackground">?android:colorBackground</item> + </style> +</resources>
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/values/styles.xml b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off --> + <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar"> + <!-- Show a splash screen on the activity. Automatically removed when + the Flutter engine draws its first frame --> + <item name="android:windowBackground">@drawable/launch_background</item> + </style> + <!-- Theme applied to the Android Window as soon as the process has started. + This theme determines the color of the Android Window while your + Flutter UI initializes, as well as behind your Flutter UI while its + running. + + This Theme is only used starting with V2 of Flutter's Android embedding. --> + <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar"> + <item name="android:windowBackground">?android:colorBackground</item> + </style> +</resources>
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/profile/AndroidManifest.xml b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..822a208 --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/example/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,8 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.example.notification_plugin_example"> + <!-- The INTERNET permission is required for development. Specifically, + the Flutter tool needs it to communicate with the running application + to allow setting breakpoints, to provide hot reload, etc. + --> + <uses-permission android:name="android.permission.INTERNET"/> +</manifest>
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/build.gradle b/pkgs/jni_gen/examples/notification_plugin/example/android/build.gradle new file mode 100644 index 0000000..83ae220 --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/example/android/build.gradle
@@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.6.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.1.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +}
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/gradle.properties b/pkgs/jni_gen/examples/notification_plugin/example/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/example/android/gradle.properties
@@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/gradle/wrapper/gradle-wrapper.properties b/pkgs/jni_gen/examples/notification_plugin/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..cb24abd --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/android/settings.gradle b/pkgs/jni_gen/examples/notification_plugin/example/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/example/android/settings.gradle
@@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/lib/main.dart b/pkgs/jni_gen/examples/notification_plugin/example/lib/main.dart new file mode 100644 index 0000000..4bc30d4 --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/example/lib/main.dart
@@ -0,0 +1,89 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:jni/jni.dart'; + +// The hierarchy created in generated code will mirror the java package +// structure. This is an implementation convenience and we may allow +// more customization in future. +import 'package:notification_plugin/com/example/notification_plugin.dart'; + +JlObject activity = JlObject.fromRef(Jni.getInstance().getCurrentActivity()); + +int i = 0; + +void showNotification(String title, String text) { + i = i + 1; + var jTitle = JlString.fromString(title); + var jText = JlString.fromString(text); + Notifications.showNotification(activity, i, jTitle, jText); + jTitle.delete(); + jText.delete(); +} + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + primarySwatch: Colors.teal, + ), + home: MyHomePage(title: 'Flutter Demo Home Page'), + ); + } +} + +class MyHomePage extends StatelessWidget { + MyHomePage({Key? key, required this.title}) : super(key: key); + + final String title; + + final _title = TextEditingController(text: 'Hello from JNI'); + final _text = TextEditingController(text: '😀'); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(title), + ), + body: Center( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextFormField( + controller: _title, + textCapitalization: TextCapitalization.sentences, + decoration: + const InputDecoration(labelText: 'Notification title'), + ), + TextFormField( + controller: _text, + keyboardType: TextInputType.multiline, + minLines: 1, + maxLines: 4, + decoration: + const InputDecoration(labelText: 'Notification text'), + ), + ElevatedButton( + child: const Text('Show Notification'), + onPressed: () => showNotification(_title.text, _text.text), + ), + ], + ), + ), + ), + ); + } +}
diff --git a/pkgs/jni_gen/examples/notification_plugin/example/pubspec.yaml b/pkgs/jni_gen/examples/notification_plugin/example/pubspec.yaml new file mode 100644 index 0000000..b1f6a97 --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/example/pubspec.yaml
@@ -0,0 +1,89 @@ +name: notification_plugin_example +description: Demonstrates how to use the notification_plugin plugin. + +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: '>=2.18.0 <3.0.0' + +dependencies: + flutter: + sdk: flutter + + notification_plugin: + path: ../ + + jni: + path: ../../../../jni/ + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages
diff --git a/pkgs/jni_gen/examples/notification_plugin/jnigen.yaml b/pkgs/jni_gen/examples/notification_plugin/jnigen.yaml new file mode 100644 index 0000000..16315f6 --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/jnigen.yaml
@@ -0,0 +1,17 @@ +android_sdk_config: + add_gradle_deps: true + android_example: 'example/' + +preamble: | + // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file + // for details. All rights reserved. Use of this source code is governed by a + // BSD-style license that can be found in the LICENSE file. + +library_name: notification_plugin +source_path: + - 'android/src/main/java' +classes: + - 'com.example.notification_plugin.Notifications' +c_root: src/ +dart_root: lib/ +
diff --git a/pkgs/jni_gen/examples/notification_plugin/lib/com/example/notification_plugin.dart b/pkgs/jni_gen/examples/notification_plugin/lib/com/example/notification_plugin.dart new file mode 100644 index 0000000..52c75af --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/lib/com/example/notification_plugin.dart
@@ -0,0 +1,46 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// Autogenerated by jni_gen. DO NOT EDIT! + +// ignore_for_file: camel_case_types +// ignore_for_file: non_constant_identifier_names +// ignore_for_file: constant_identifier_names +// ignore_for_file: annotate_overrides +// ignore_for_file: no_leading_underscores_for_local_identifiers +// ignore_for_file: unused_element + +import "dart:ffi" as ffi; + +import "package:jni/jni.dart" as jni; + +import "../../init.dart" show jlookup; + +/// from: com.example.notification_plugin.Notifications +class Notifications extends jni.JlObject { + Notifications.fromRef(ffi.Pointer<ffi.Void> ref) : super.fromRef(ref); + + static final _ctor = + jlookup<ffi.NativeFunction<ffi.Pointer<ffi.Void> Function()>>( + "com_example_notification_plugin_Notifications_ctor") + .asFunction<ffi.Pointer<ffi.Void> Function()>(); + + /// from: public void <init>() + Notifications() : super.fromRef(_ctor()); + + static final _showNotification = jlookup< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer<ffi.Void>, ffi.Int32, + ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>( + "com_example_notification_plugin_Notifications_showNotification") + .asFunction< + void Function(ffi.Pointer<ffi.Void>, int, ffi.Pointer<ffi.Void>, + ffi.Pointer<ffi.Void>)>(); + + /// from: static public void showNotification(android.content.Context context, int notificationID, java.lang.String title, java.lang.String text) + static void showNotification(jni.JlObject context, int notificationID, + jni.JlString title, jni.JlString text) => + _showNotification( + context.reference, notificationID, title.reference, text.reference); +}
diff --git a/pkgs/jni_gen/examples/notification_plugin/lib/init.dart b/pkgs/jni_gen/examples/notification_plugin/lib/init.dart new file mode 100644 index 0000000..9a4243f --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/lib/init.dart
@@ -0,0 +1,5 @@ +import "dart:ffi"; +import "package:jni/jni.dart"; + +final Pointer<T> Function<T extends NativeType>(String sym) jlookup = + Jni.getInstance().initGeneratedLibrary("notification_plugin");
diff --git a/pkgs/jni_gen/examples/notification_plugin/pubspec.yaml b/pkgs/jni_gen/examples/notification_plugin/pubspec.yaml new file mode 100644 index 0000000..422f83a --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/pubspec.yaml
@@ -0,0 +1,76 @@ +name: notification_plugin +description: A new Flutter FFI plugin project. +version: 0.0.1 +publish_to: none +homepage: https://github.com/dart-lang/jni_gen + +environment: + sdk: '>=2.18.0 <3.0.0' + flutter: ">=2.11.0" + +dependencies: + jni: + path: ../../../jni/ + flutter: + sdk: flutter + plugin_platform_interface: ^2.0.2 + +dev_dependencies: + jni_gen: + path: ../../ + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) + # which should be registered in the plugin registry. This is required for + # using method channels. + # The Android 'package' specifies package in which the registered class is. + # This is required for using method channels on Android. + # The 'ffiPlugin' specifies that native code should be built and bundled. + # This is required for using `dart:ffi`. + # All these are used by the tooling to maintain consistency when + # adding or updating assets for this project. + # + # Please refer to README.md for a detailed explanation. + plugin: + platforms: + android: + ffiPlugin: true + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your plugin package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages
diff --git a/pkgs/jni_gen/examples/notification_plugin/src/CMakeLists.txt b/pkgs/jni_gen/examples/notification_plugin/src/CMakeLists.txt new file mode 100644 index 0000000..c733845 --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/src/CMakeLists.txt
@@ -0,0 +1,30 @@ +# The Flutter tooling requires that developers have CMake 3.10 or later +# installed. You should not increase this version, as doing so will cause +# the plugin to fail to compile for some customers of the plugin. +cmake_minimum_required(VERSION 3.10) + +project(notification_plugin VERSION 0.0.1 LANGUAGES C) + +add_library(notification_plugin SHARED + "./notification_plugin.c" +) + +set_target_properties(notification_plugin PROPERTIES + OUTPUT_NAME "notification_plugin" +) + +target_compile_definitions(notification_plugin PUBLIC DART_SHARED_LIB) + +if(WIN32) + set_target_properties(${TARGET_NAME} PROPERTIES + LINK_FLAGS "/DELAYLOAD:jvm.dll") +endif() + +if (ANDROID) + target_link_libraries(notification_plugin log) +else() + find_package(Java REQUIRED) + find_package(JNI REQUIRED) + include_directories(${JNI_INCLUDE_DIRS}) + target_link_libraries(notification_plugin ${JNI_LIBRARIES}) +endif()
diff --git a/pkgs/jni_gen/examples/notification_plugin/src/dartjni.h b/pkgs/jni_gen/examples/notification_plugin/src/dartjni.h new file mode 100644 index 0000000..cd94b15 --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/src/dartjni.h
@@ -0,0 +1,177 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#include <jni.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> + +#if _WIN32 +#include <windows.h> +#else +#include <pthread.h> +#include <unistd.h> +#endif + +#if _WIN32 +#define FFI_PLUGIN_EXPORT __declspec(dllexport) +#else +#define FFI_PLUGIN_EXPORT +#endif + +#if defined _WIN32 +#define thread_local __declspec(thread) +#else +#define thread_local __thread +#endif + +#ifdef __ANDROID__ +#include <android/log.h> +#endif + +#define JNI_LOG_TAG "Dart-JNI" + +#ifdef __ANDROID__ +#define __ENVP_CAST (JNIEnv **) +#else +#define __ENVP_CAST (void **) +#endif + +struct jni_context { + JavaVM *jvm; + jobject classLoader; + jmethodID loadClassMethod; + jobject currentActivity; + jobject appContext; +}; + +extern thread_local JNIEnv *jniEnv; + +extern struct jni_context jni; + +enum DartJniLogLevel { + JNI_VERBOSE = 2, + JNI_DEBUG, + JNI_INFO, + JNI_WARN, + JNI_ERROR +}; + +FFI_PLUGIN_EXPORT struct jni_context GetJniContext(); + +FFI_PLUGIN_EXPORT JavaVM *GetJavaVM(void); + +FFI_PLUGIN_EXPORT JNIEnv *GetJniEnv(void); + +FFI_PLUGIN_EXPORT JNIEnv *SpawnJvm(JavaVMInitArgs *args); + +FFI_PLUGIN_EXPORT jclass LoadClass(const char *name); + +FFI_PLUGIN_EXPORT jobject GetClassLoader(void); + +FFI_PLUGIN_EXPORT jobject GetApplicationContext(void); + +FFI_PLUGIN_EXPORT jobject GetCurrentActivity(void); + +FFI_PLUGIN_EXPORT void SetJNILogging(int level); + +FFI_PLUGIN_EXPORT jstring ToJavaString(char *str); + +FFI_PLUGIN_EXPORT const char *GetJavaStringChars(jstring jstr); + +FFI_PLUGIN_EXPORT void ReleaseJavaStringChars(jstring jstr, const char *buf); + +// These 2 are the function pointer variables defined and exported by +// the generated C files. +// +// initGeneratedLibrary function in Jni class will set these to +// corresponding functions to the implementations from `dartjni` base library +// which initializes and manages the JNI. +extern struct jni_context (*context_getter)(void); +extern JNIEnv *(*env_getter)(void); + +// This function will be exported by generated code library and will set the +// above 2 variables. +FFI_PLUGIN_EXPORT void setJniGetters(struct jni_context (*cg)(void), + JNIEnv *(*eg)(void)); + +// `static inline` because `inline` doesn't work, it may still not +// inline the function in which case a linker error may be produced. +// +// There has to be a better way to do this. Either to force inlining on target +// platforms, or just leave it as normal function. +static inline void __load_class_into(jclass *cls, const char *name) { +#ifdef __ANDROID__ + jstring className = (*jniEnv)->NewStringUTF(jniEnv, name); + *cls = (*jniEnv)->CallObjectMethod(jniEnv, jni.classLoader, + jni.loadClassMethod, className); + (*jniEnv)->DeleteLocalRef(jniEnv, className); +#else + *cls = (*jniEnv)->FindClass(jniEnv, name); +#endif +} + +static inline void load_class(jclass *cls, const char *name) { + if (*cls == NULL) { + __load_class_into(cls, name); + } +} + +static inline void load_class_gr(jclass *cls, const char *name) { + if (*cls == NULL) { + jclass tmp; + __load_class_into(&tmp, name); + *cls = (*jniEnv)->NewGlobalRef(jniEnv, tmp); + (*jniEnv)->DeleteLocalRef(jniEnv, tmp); + } +} + +static inline void attach_thread() { + if (jniEnv == NULL) { + (*jni.jvm)->AttachCurrentThread(jni.jvm, __ENVP_CAST & jniEnv, + NULL); + } +} + +static inline void load_env() { + if (jniEnv == NULL) { + jni = context_getter(); + jniEnv = env_getter(); + } +} + +static inline void load_method(jclass cls, jmethodID *res, const char *name, + const char *sig) { + if (*res == NULL) { + *res = (*jniEnv)->GetMethodID(jniEnv, cls, name, sig); + } +} + +static inline void load_static_method(jclass cls, jmethodID *res, + const char *name, const char *sig) { + if (*res == NULL) { + *res = (*jniEnv)->GetStaticMethodID(jniEnv, cls, name, sig); + } +} + +static inline void load_field(jclass cls, jfieldID *res, const char *name, + const char *sig) { + if (*res == NULL) { + *res = (*jniEnv)->GetFieldID(jniEnv, cls, name, sig); + } +} + +static inline void load_static_field(jclass cls, jfieldID *res, + const char *name, const char *sig) { + if (*res == NULL) { + *res = (*jniEnv)->GetStaticFieldID(jniEnv, cls, name, sig); + } +} + +static inline jobject to_global_ref(jobject ref) { + jobject g = (*jniEnv)->NewGlobalRef(jniEnv, ref); + (*jniEnv)->DeleteLocalRef(jniEnv, ref); + return g; +} +
diff --git a/pkgs/jni_gen/examples/notification_plugin/src/notification_plugin.c b/pkgs/jni_gen/examples/notification_plugin/src/notification_plugin.c new file mode 100644 index 0000000..6457cae --- /dev/null +++ b/pkgs/jni_gen/examples/notification_plugin/src/notification_plugin.c
@@ -0,0 +1,44 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// Autogenerated by jni_gen. DO NOT EDIT! + +#include <stdint.h> +#include "jni.h" +#include "dartjni.h" + +thread_local JNIEnv *jniEnv; +struct jni_context jni; + +struct jni_context (*context_getter)(void); +JNIEnv *(*env_getter)(void); + +void setJniGetters(struct jni_context (*cg)(void), + JNIEnv *(*eg)(void)) { + context_getter = cg; + env_getter = eg; +} + +// com.example.notification_plugin.Notifications +jclass _c_com_example_notification_plugin_Notifications = NULL; + +jmethodID _m_com_example_notification_plugin_Notifications_ctor = NULL; +FFI_PLUGIN_EXPORT +jobject com_example_notification_plugin_Notifications_ctor() { + load_env(); + load_class_gr(&_c_com_example_notification_plugin_Notifications, "com/example/notification_plugin/Notifications"); + load_method(_c_com_example_notification_plugin_Notifications, &_m_com_example_notification_plugin_Notifications_ctor, "<init>", "()V"); + jobject _result = (*jniEnv)->NewObject(jniEnv, _c_com_example_notification_plugin_Notifications, _m_com_example_notification_plugin_Notifications_ctor); + return to_global_ref(_result); +} + +jmethodID _m_com_example_notification_plugin_Notifications_showNotification = NULL; +FFI_PLUGIN_EXPORT +void com_example_notification_plugin_Notifications_showNotification(jobject context, int32_t notificationID, jobject title, jobject text) { + load_env(); + load_class_gr(&_c_com_example_notification_plugin_Notifications, "com/example/notification_plugin/Notifications"); + load_static_method(_c_com_example_notification_plugin_Notifications, &_m_com_example_notification_plugin_Notifications_showNotification, "showNotification", "(Landroid/content/Context;ILjava/lang/String;Ljava/lang/String;)V"); + (*jniEnv)->CallStaticVoidMethod(jniEnv, _c_com_example_notification_plugin_Notifications, _m_com_example_notification_plugin_Notifications_showNotification, context, notificationID, title, text); +} +
diff --git a/pkgs/jni_gen/examples/pdfbox_plugin/dart_example/bin/pdf_info.dart b/pkgs/jni_gen/examples/pdfbox_plugin/dart_example/bin/pdf_info.dart index f8756ff..f276ea1 100644 --- a/pkgs/jni_gen/examples/pdfbox_plugin/dart_example/bin/pdf_info.dart +++ b/pkgs/jni_gen/examples/pdfbox_plugin/dart_example/bin/pdf_info.dart
@@ -1,3 +1,7 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + import 'dart:io'; import 'package:path/path.dart';
diff --git a/pkgs/jni_gen/examples/pdfbox_plugin/example/lib/main.dart b/pkgs/jni_gen/examples/pdfbox_plugin/example/lib/main.dart index 1557b84..09527fb 100644 --- a/pkgs/jni_gen/examples/pdfbox_plugin/example/lib/main.dart +++ b/pkgs/jni_gen/examples/pdfbox_plugin/example/lib/main.dart
@@ -1,3 +1,7 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + import 'package:flutter/material.dart'; import 'dart:io'; import 'dart:async';
diff --git a/pkgs/jni_gen/examples/pdfbox_plugin/tool/generate_bindings.dart b/pkgs/jni_gen/examples/pdfbox_plugin/tool/generate_bindings.dart index b7ea48e..547a207 100644 --- a/pkgs/jni_gen/examples/pdfbox_plugin/tool/generate_bindings.dart +++ b/pkgs/jni_gen/examples/pdfbox_plugin/tool/generate_bindings.dart
@@ -1,3 +1,7 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + import 'package:jni_gen/jni_gen.dart'; const preamble = ''' @@ -31,7 +35,9 @@ ), classes: ['org.apache.pdfbox.pdmodel', 'org.apache.pdfbox.text'], cRoot: Uri.directory('src/'), - dartRoot: Uri.directory('lib/'), + cSubdir: 'third_party', + preamble: preamble, + dartRoot: Uri.directory('lib/third_party'), libraryName: 'pdfbox_plugin', ); await generateJniBindings(config);
diff --git a/pkgs/jni_gen/lib/src/config/config.dart b/pkgs/jni_gen/lib/src/config/config.dart index 278e1bd..6d18085 100644 --- a/pkgs/jni_gen/lib/src/config/config.dart +++ b/pkgs/jni_gen/lib/src/config/config.dart
@@ -56,20 +56,49 @@ /// environment variable `ANDROID_SDK_ROOT` if it's defined, else an error /// will be thrown. class AndroidSdkConfig { - AndroidSdkConfig( - {this.versions, - this.sdkRoot, - this.includeSources = false, - this.addGradleDeps = false}) { + AndroidSdkConfig({ + this.versions, + this.sdkRoot, + this.includeSources = false, + this.addGradleDeps = false, + this.androidExample, + }) { if (versions == null && !addGradleDeps) { throw ArgumentError('Neither any SDK versions nor `addGradleDeps` ' 'is specified. Unable to find Android libraries.'); } } + + /// Versions of android SDK to search for, in decreasing order of preference. List<int>? versions; + + /// Root of Android SDK installation, this should be normally given on + /// command line or by setting `ANDROID_SDK_ROOT`, since this varies from + /// system to system. String? sdkRoot; + + /// Include downloaded android SDK sources in source path. bool includeSources; + + /// Attempt to determine exact compile time dependencies by running a gradle + /// stub in android subproject of this project. + /// + /// An Android build must have happened before we are able to obtain classpath + /// of Gradle dependencies. Run `flutter build apk` before running a jni_gen + /// script with this option. + /// + /// For the same reason, if the flutter project is a plugin instead of + /// application, it's not possible to determine the build classpath directly. + /// Please provide [androidExample] pointing to an example application in + /// that case. bool addGradleDeps; + + /// Relative path to example application which will be used to determine + /// compile time classpath using a gradle stub. For most Android plugin + /// packages, 'example' will be the name of example application created inside + /// the package. This example should be built once before using this option, + /// so that gradle would have resolved all the dependencies. + String? androidExample; } /// Additional options to pass to the summary generator component. @@ -263,6 +292,7 @@ includeSources: prov.getBool(_Props.includeAndroidSources) ?? false, addGradleDeps: prov.getBool(_Props.addGradleDeps) ?? false, + androidExample: prov.getString(_Props.androidExample), ) : null, ); @@ -315,4 +345,5 @@ static const androidSdkVersions = '$androidSdkConfig.versions'; static const includeAndroidSources = '$androidSdkConfig.include_sources'; static const addGradleDeps = '$androidSdkConfig.add_gradle_deps'; + static const androidExample = '$androidSdkConfig.android_example'; }
diff --git a/pkgs/jni_gen/lib/src/generate_bindings.dart b/pkgs/jni_gen/lib/src/generate_bindings.dart index 09f948f..c6f410e 100644 --- a/pkgs/jni_gen/lib/src/generate_bindings.dart +++ b/pkgs/jni_gen/lib/src/generate_bindings.dart
@@ -45,7 +45,8 @@ final androidConfig = config.androidSdkConfig; if (androidConfig != null && androidConfig.addGradleDeps) { - final deps = AndroidSdkTools.getGradleClasspaths(); + final deps = AndroidSdkTools.getGradleClasspaths( + androidConfig.androidExample ?? '.'); extraJars.addAll(deps.map(Uri.file)); } if (androidConfig != null && androidConfig.versions != null) {
diff --git a/pkgs/jni_gen/lib/src/tools/android_sdk_tools.dart b/pkgs/jni_gen/lib/src/tools/android_sdk_tools.dart index 029555a..ed78566 100644 --- a/pkgs/jni_gen/lib/src/tools/android_sdk_tools.dart +++ b/pkgs/jni_gen/lib/src/tools/android_sdk_tools.dart
@@ -63,10 +63,13 @@ /// /// This function temporarily overwrites the build.gradle file by a stub with /// function to list all dependency paths for release variant. - /// This function fails if no gradle build is attempted - static List<String> getGradleClasspaths() { - stderr.writeln('trying to obtain gradle classpaths'); - const android = 'android'; + /// This function fails if no gradle build is attempted before. + /// + /// If current project is not directly buildable by gradle, eg: a plugin, + /// a relative path to other project can be specified using [androidProject]. + static List<String> getGradleClasspaths([String androidProject = '.']) { + stderr.writeln('trying to obtain gradle classpaths...'); + final android = join(androidProject, 'android'); final buildGradle = join(android, 'build.gradle'); final buildGradleOld = join(android, 'build.gradle.old'); final origBuild = File(buildGradle); @@ -80,8 +83,8 @@ File(buildGradleOld).deleteSync(); if (procRes.exitCode != 0) { throw Exception('\n\ngradle exited with exit code ${procRes.exitCode}\n' - 'This can be related to a known issue with gradle. ' - 'Please run `flutter build apk` and try again\n'); + 'This can be related to a known issue with gradle. Please run ' + '`flutter build apk` in $androidProject and try again\n'); } final gradleClassPaths = (procRes.stdout as String).split('\n'); if (gradleClassPaths.last.isEmpty) {