diff --git a/README.md b/README.md index c8daae5..e032cad 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,7 @@ Platform-specific code for Moxxy. Not intended for use outside of Moxxy. This repo is based on [very_good_flutter_plugin](https://github.com/VeryGoodOpenSource/very_good_flutter_plugin). + +## Acknowledgements + +- [ekasetiawans](https://github.com/ekasetiawans) for [flutter_background_service](https://github.com/ekasetiawans/flutter_background_service). moxplatform_android is basically just a copy and paste of [flutter_background_service](https://github.com/ekasetiawans/flutter_background_service). diff --git a/src/moxplatform/example/.gitignore b/src/moxplatform/example/.gitignore new file mode 100644 index 0000000..a8e938c --- /dev/null +++ b/src/moxplatform/example/.gitignore @@ -0,0 +1,47 @@ +# 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/ + +# Web related +lib/generated_plugin_registrant.dart + +# 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/src/moxplatform/example/.metadata b/src/moxplatform/example/.metadata new file mode 100644 index 0000000..625541c --- /dev/null +++ b/src/moxplatform/example/.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: 13a2fb10b838971ce211230f8ffdd094c14af02c + channel: beta + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 13a2fb10b838971ce211230f8ffdd094c14af02c + base_revision: 13a2fb10b838971ce211230f8ffdd094c14af02c + - platform: android + create_revision: 13a2fb10b838971ce211230f8ffdd094c14af02c + base_revision: 13a2fb10b838971ce211230f8ffdd094c14af02c + + # 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/src/moxplatform/example/README.md b/src/moxplatform/example/README.md new file mode 100644 index 0000000..35a888b --- /dev/null +++ b/src/moxplatform/example/README.md @@ -0,0 +1,16 @@ +# moxplatform_example + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/src/moxplatform/example/analysis_options.yaml b/src/moxplatform/example/analysis_options.yaml new file mode 100644 index 0000000..61b6c4d --- /dev/null +++ b/src/moxplatform/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/src/moxplatform/example/android/.gitignore b/src/moxplatform/example/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/src/moxplatform/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/src/moxplatform/example/android/app/build.gradle b/src/moxplatform/example/android/app/build.gradle new file mode 100644 index 0000000..60f01bf --- /dev/null +++ b/src/moxplatform/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 "me.polynom.moxplatform_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/src/moxplatform/example/android/app/src/debug/AndroidManifest.xml b/src/moxplatform/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..2f018d8 --- /dev/null +++ b/src/moxplatform/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/src/moxplatform/example/android/app/src/main/AndroidManifest.xml b/src/moxplatform/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..777ea34 --- /dev/null +++ b/src/moxplatform/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/src/moxplatform/example/android/app/src/main/kotlin/me/polynom/moxplatform_example/MainActivity.kt b/src/moxplatform/example/android/app/src/main/kotlin/me/polynom/moxplatform_example/MainActivity.kt new file mode 100644 index 0000000..d8808f5 --- /dev/null +++ b/src/moxplatform/example/android/app/src/main/kotlin/me/polynom/moxplatform_example/MainActivity.kt @@ -0,0 +1,6 @@ +package me.polynom.moxplatform_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/src/moxplatform/example/android/app/src/main/res/drawable-v21/launch_background.xml b/src/moxplatform/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/src/moxplatform/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/src/moxplatform/example/android/app/src/main/res/drawable/launch_background.xml b/src/moxplatform/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/src/moxplatform/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/src/moxplatform/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/src/moxplatform/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/src/moxplatform/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/moxplatform/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/src/moxplatform/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/src/moxplatform/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/moxplatform/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/src/moxplatform/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/src/moxplatform/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/moxplatform/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/src/moxplatform/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/src/moxplatform/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/moxplatform/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/src/moxplatform/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/src/moxplatform/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/moxplatform/example/android/app/src/main/res/values-night/styles.xml b/src/moxplatform/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/src/moxplatform/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/src/moxplatform/example/android/app/src/main/res/values/styles.xml b/src/moxplatform/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/src/moxplatform/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/src/moxplatform/example/android/app/src/profile/AndroidManifest.xml b/src/moxplatform/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..2f018d8 --- /dev/null +++ b/src/moxplatform/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/src/moxplatform/example/android/build.gradle b/src/moxplatform/example/android/build.gradle new file mode 100644 index 0000000..83ae220 --- /dev/null +++ b/src/moxplatform/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/src/moxplatform/example/android/gradle.properties b/src/moxplatform/example/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/src/moxplatform/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/src/moxplatform/example/android/gradle/wrapper/gradle-wrapper.properties b/src/moxplatform/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..cc5527d --- /dev/null +++ b/src/moxplatform/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +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/src/moxplatform/example/android/settings.gradle b/src/moxplatform/example/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/src/moxplatform/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/src/moxplatform/example/lib/main.dart b/src/moxplatform/example/lib/main.dart new file mode 100644 index 0000000..dfdd148 --- /dev/null +++ b/src/moxplatform/example/lib/main.dart @@ -0,0 +1,174 @@ +import "dart:async"; +import 'package:flutter/material.dart'; + +import "package:moxplatform/moxplatform.dart"; +import "package:moxplatform_platform_interface/src/service.dart"; +import "package:moxplatform/types.dart"; +import "package:moxlib/awaitabledatasender.dart"; +import "package:get_it/get_it.dart"; + +class TestEvent extends BackgroundEvent implements JsonImplementation { + TestEvent(); + + // JSON stuff + Map toJson() => { + "test": "hallo", + "welt": "yes", + "type": "TestEvent" + }; + static TestEvent fromJson(Map json) => TestEvent(); +} + +class TestCommand extends BackgroundCommand implements JsonImplementation { + TestCommand(); + + // JSON stuff + Map toJson() => { + "test": "hallo", + "welt": "yes", + "type": "TestEvent" + }; + static TestEvent fromJson(Map json) => TestEvent(); +} + +void main() { + runApp(const MyApp()); +} + +Future entrypoint() async { + print("Hallo Welt from new FlutterEngine"); + + final srv = GetIt.I.get(); + Timer.periodic(const Duration(seconds: 2), (_) { + srv.sendEvent(TestEvent()); + }); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + // This is the theme of your application. + // + // Try running your application with "flutter run". You'll see the + // application has a blue toolbar. Then, without quitting the app, try + // changing the primarySwatch below to Colors.green and then invoke + // "hot reload" (press "r" in the console where you ran "flutter run", + // or simply save your changes to "hot reload" in a Flutter IDE). + // Notice that the counter didn't reset back to zero; the application + // is not restarted. + primarySwatch: Colors.blue, + ), + home: const MyHomePage(title: 'Flutter Demo Home Page'), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({Key? key, required this.title}) : super(key: key); + + // This widget is the home page of your application. It is stateful, meaning + // that it has a State object (defined below) that contains fields that affect + // how it looks. + + // This class is the configuration for the state. It holds the values (in this + // case the title) provided by the parent (in this case the App widget) and + // used by the build method of the State. Fields in a Widget subclass are + // always marked "final". + + final String title; + + @override + State createState() => _MyHomePageState(); +} + +Future uiData(Map? data) async { + print("Isolate got data: $data"); +} +Future isolateData(Map? data) async { + print("UI got data: $data"); +} + +class _MyHomePageState extends State { + int _counter = 0; + + void _incrementCounter() { + MoxplatformPlugin.handler.start( + entrypoint, + uiData, + isolateData, + ); + + Timer(const Duration(seconds: 3), () { + MoxplatformPlugin.handler.getDataSender().sendData( + TestCommand() + ); + }); + + setState(() { + // This call to setState tells the Flutter framework that something has + // changed in this State, which causes it to rerun the build method below + // so that the display can reflect the updated values. If we changed + // _counter without calling setState(), then the build method would not be + // called again, and so nothing would appear to happen. + _counter++; + }); + } + + @override + Widget build(BuildContext context) { + // This method is rerun every time setState is called, for instance as done + // by the _incrementCounter method above. + // + // The Flutter framework has been optimized to make rerunning build methods + // fast, so that you can just rebuild anything that needs updating rather + // than having to individually change instances of widgets. + return Scaffold( + appBar: AppBar( + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: Text(widget.title), + ), + body: Center( + // Center is a layout widget. It takes a single child and positions it + // in the middle of the parent. + child: Column( + // Column is also a layout widget. It takes a list of children and + // arranges them vertically. By default, it sizes itself to fit its + // children horizontally, and tries to be as tall as its parent. + // + // Invoke "debug painting" (press "p" in the console, choose the + // "Toggle Debug Paint" action from the Flutter Inspector in Android + // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) + // to see the wireframe for each widget. + // + // Column has various properties to control how it sizes itself and + // how it positions its children. Here we use mainAxisAlignment to + // center the children vertically; the main axis here is the vertical + // axis because Columns are vertical (the cross axis would be + // horizontal). + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'You have pushed the button this many times:', + ), + Text( + '$_counter', + style: Theme.of(context).textTheme.headline4, + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: _incrementCounter, + tooltip: 'Increment', + child: const Icon(Icons.add), + ), // This trailing comma makes auto-formatting nicer for build methods. + ); + } +} diff --git a/src/moxplatform/example/pubspec.yaml b/src/moxplatform/example/pubspec.yaml new file mode 100644 index 0000000..6328887 --- /dev/null +++ b/src/moxplatform/example/pubspec.yaml @@ -0,0 +1,92 @@ +name: moxplatform_example +description: A new Flutter project. + +# 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 used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 1.0.0+1 + +environment: + sdk: ">=2.17.0-266.1.beta <3.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + + # 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 + + moxplatform: + path: ../ + + # 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/src/moxplatform/example/test/widget_test.dart b/src/moxplatform/example/test/widget_test.dart new file mode 100644 index 0000000..af3acd6 --- /dev/null +++ b/src/moxplatform/example/test/widget_test.dart @@ -0,0 +1,30 @@ +// 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:moxplatform_example/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/src/moxplatform/pubspec.yaml b/src/moxplatform/pubspec.yaml index 6593ee8..009ac73 100644 --- a/src/moxplatform/pubspec.yaml +++ b/src/moxplatform/pubspec.yaml @@ -21,15 +21,17 @@ dependencies: url: https://codeberg.org/moxxy/moxlib.git rev: 020c592d459654b7a807f115bd70809b3cf522bd moxplatform_android: - git: - url: https://codeberg.org/moxxy/moxplatform.git - rev: c19ae302bb8d2e2bd8db220cc6a9a488c33e05d1 - path: src/moxplatform_android + #git: + # url: https://codeberg.org/moxxy/moxplatform.git + # rev: c19ae302bb8d2e2bd8db220cc6a9a488c33e05d1 + # path: src/moxplatform_android + path: ../moxplatform_android moxplatform_platform_interface: - git: - url: https://codeberg.org/moxxy/moxplatform.git - rev: c19ae302bb8d2e2bd8db220cc6a9a488c33e05d1 - path: src/moxplatform_platform_interface + #git: + # url: https://codeberg.org/moxxy/moxplatform.git + # rev: c19ae302bb8d2e2bd8db220cc6a9a488c33e05d1 + # path: src/moxplatform_platform_interface + path: ../moxplatform_platform_interface dev_dependencies: flutter_test: diff --git a/src/moxplatform_android/.gitignore b/src/moxplatform_android/.gitignore new file mode 100644 index 0000000..96486fd --- /dev/null +++ b/src/moxplatform_android/.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/src/moxplatform_android/.metadata b/src/moxplatform_android/.metadata new file mode 100644 index 0000000..727dacb --- /dev/null +++ b/src/moxplatform_android/.metadata @@ -0,0 +1,10 @@ +# 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 and should not be manually edited. + +version: + revision: 13a2fb10b838971ce211230f8ffdd094c14af02c + channel: beta + +project_type: plugin diff --git a/src/moxplatform_android/README.md b/src/moxplatform_android/README.md new file mode 100644 index 0000000..1e4b634 --- /dev/null +++ b/src/moxplatform_android/README.md @@ -0,0 +1,15 @@ +# moxplatform_android + +A new Flutter plugin project. + +## Getting Started + +This project is a starting point for a Flutter +[plug-in package](https://flutter.dev/developing-packages/), +a specialized package that includes platform-specific implementation code for +Android and/or iOS. + +For help getting started with Flutter development, view the +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. + diff --git a/src/moxplatform_android/analysis_options.yaml b/src/moxplatform_android/analysis_options.yaml new file mode 100644 index 0000000..a5744c1 --- /dev/null +++ b/src/moxplatform_android/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/src/moxplatform_android/android/.gitignore b/src/moxplatform_android/android/.gitignore index 2665975..161bdcd 100644 --- a/src/moxplatform_android/android/.gitignore +++ b/src/moxplatform_android/android/.gitignore @@ -5,4 +5,5 @@ /.idea/libraries .DS_Store /build -/captures \ No newline at end of file +/captures +.cxx diff --git a/src/moxplatform_android/android/build.gradle b/src/moxplatform_android/android/build.gradle index afe7670..b476504 100644 --- a/src/moxplatform_android/android/build.gradle +++ b/src/moxplatform_android/android/build.gradle @@ -1,8 +1,7 @@ -group 'com.example.my_plugin' -version '1.0-SNAPSHOT' +group 'me.polynom.moxplatform_android' +version '1.0' buildscript { - ext.kotlin_version = '1.3.50' repositories { google() mavenCentral() @@ -10,7 +9,6 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:4.1.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -22,29 +20,20 @@ rootProject.allprojects { } apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' android { - compileSdkVersion 30 + compileSdkVersion 31 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - defaultConfig { minSdkVersion 16 } } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' } \ No newline at end of file diff --git a/src/moxplatform_android/android/gradle/wrapper/gradle-wrapper.properties b/src/moxplatform_android/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..da9702f --- /dev/null +++ b/src/moxplatform_android/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/src/moxplatform_android/android/settings.gradle b/src/moxplatform_android/android/settings.gradle index cbb9c70..77b21db 100644 --- a/src/moxplatform_android/android/settings.gradle +++ b/src/moxplatform_android/android/settings.gradle @@ -1 +1 @@ -rootProject.name = 'my_plugin_android' +rootProject.name = 'moxplatform_android' diff --git a/src/moxplatform_android/android/src/main/AndroidManifest.xml b/src/moxplatform_android/android/src/main/AndroidManifest.xml index c17e2b7..2c55e6b 100644 --- a/src/moxplatform_android/android/src/main/AndroidManifest.xml +++ b/src/moxplatform_android/android/src/main/AndroidManifest.xml @@ -1,3 +1,31 @@ + package="me.polynom.moxplatform_android"> + + + + + + + + + + + + + + + + + + diff --git a/src/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/BackgroundService.java b/src/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/BackgroundService.java new file mode 100644 index 0000000..aaec5d0 --- /dev/null +++ b/src/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/BackgroundService.java @@ -0,0 +1,262 @@ +package me.polynom.moxplatform_android; + +import android.app.AlarmManager; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Build; +import android.os.IBinder; +import android.os.Looper; +import android.os.PowerManager; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.core.app.AlarmManagerCompat; +import androidx.core.app.NotificationCompat; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicBoolean; + +import io.flutter.FlutterInjector; +import io.flutter.app.FlutterApplication; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.embedding.engine.dart.DartExecutor; +import io.flutter.embedding.engine.loader.FlutterLoader; +import io.flutter.plugin.common.JSONMethodCodec; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.view.FlutterCallbackInformation; +import io.flutter.view.FlutterMain; + +public class BackgroundService extends Service implements MethodChannel.MethodCallHandler { + private static String TAG = "BackgroundService"; + private static final String manuallyStoppedKey = "manually_stopped"; + private static final String backgroundMethodChannelKey = MoxplatformAndroidPlugin.methodChannelKey + "_bg"; + /// The [FlutterEngine] executing the background service + private FlutterEngine engine; + private MethodChannel methodChannel; + private DartExecutor.DartCallback dartCallback; + /// True if the service has been stopped by hand + private boolean isManuallyStopped = false; + /// Indicate if we're running + private AtomicBoolean isRunning = new AtomicBoolean(false); + + private static final String WAKE_LOCK_NAME = BackgroundService.class.getName() + ".Lock"; + private static volatile PowerManager.WakeLock wakeLock = null; + /// Notification data + private String notificationBody = "Preparing..."; + private static final String notificationTitle = "Moxxy"; + + synchronized private static PowerManager.WakeLock getLock(Context context) { + if (wakeLock == null) { + PowerManager mgr = (PowerManager) context + .getSystemService(Context.POWER_SERVICE); + wakeLock = mgr.newWakeLock(PowerManager.FULL_WAKE_LOCK, WAKE_LOCK_NAME); + wakeLock.setReferenceCounted(true); + } + return (wakeLock); + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + public static void enqueue(Context context) { + Intent intent = new Intent(context, WatchdogReceiver.class); + AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + + boolean aboveS = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S; + PendingIntent pending = PendingIntent.getBroadcast( + context, + 111, + intent, + PendingIntent.FLAG_UPDATE_CURRENT + // Only enable FLAG_MUTABLE when the Android version is Android S or greater + | (PendingIntent.FLAG_MUTABLE & (aboveS ? PendingIntent.FLAG_MUTABLE : 0)) + ); + AlarmManagerCompat.setAndAllowWhileIdle(manager, AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 5000, pending); + } + + public static boolean isManuallyStopped(Context context) { + return context.getSharedPreferences(MoxplatformAndroidPlugin.sharedPrefKey, MODE_PRIVATE).getBoolean(manuallyStoppedKey, false); + } + public void setManuallyStopped(Context context, boolean value) { + context.getSharedPreferences(MoxplatformAndroidPlugin.sharedPrefKey, MODE_PRIVATE) + .edit() + .putBoolean(manuallyStoppedKey, value) + .apply(); + } + + private void createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + CharSequence name = "Moxxy Background Service"; + String description = "Executing Moxxy in the background"; + + int importance = NotificationManager.IMPORTANCE_LOW; + NotificationChannel channel = new NotificationChannel("FOREGROUND_DEFAULT", name, importance); + channel.setDescription(description); + + NotificationManager notificationManager = getSystemService(NotificationManager.class); + notificationManager.createNotificationChannel(channel); + } + } + + protected void updateNotificationInfo() { + String packageName = getApplicationContext().getPackageName(); + Intent i = getPackageManager().getLaunchIntentForPackage(packageName); + + boolean aboveS = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S; + PendingIntent pending = PendingIntent.getActivity( + BackgroundService.this, + 99778, + i, + PendingIntent.FLAG_CANCEL_CURRENT + // Only enable on Android S or greater + | (PendingIntent.FLAG_MUTABLE & (aboveS ? PendingIntent.FLAG_MUTABLE : 0)) + ); + + NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this, "FOREGROUND_DEFAULT") + .setSmallIcon(R.drawable.ic_service_icon) + .setAutoCancel(true) + .setOngoing(true) + .setContentTitle(notificationTitle) + .setContentText(notificationBody) + .setContentIntent(pending); + + startForeground(99778, mBuilder.build()); + } + + private void runService() { + try { + if (isRunning.get() || (engine != null && !engine.getDartExecutor().isExecutingDart())) return; + + updateNotificationInfo(); + + // Initialize Flutter if it's not already + if (!FlutterInjector.instance().flutterLoader().initialized()) { + FlutterInjector.instance().flutterLoader().startInitialization(getApplicationContext()); + } + + long entrypointHandle = getSharedPreferences(MoxplatformAndroidPlugin.sharedPrefKey, MODE_PRIVATE) + .getLong(MoxplatformAndroidPlugin.entrypointKey, 0); + FlutterInjector.instance().flutterLoader().ensureInitializationComplete(getApplicationContext(), null); + FlutterCallbackInformation callback = FlutterCallbackInformation.lookupCallbackInformation(entrypointHandle); + if (callback == null) { + Log.e(TAG, "Callback handle not found"); + return; + } + + isRunning.set(true); + engine = new FlutterEngine(this); + engine.getServiceControlSurface().attachToService(BackgroundService.this, null, true); + + methodChannel = new MethodChannel(engine.getDartExecutor().getBinaryMessenger(), backgroundMethodChannelKey); + methodChannel.setMethodCallHandler(this); + Log.d(TAG, "Method channel is set up"); + + dartCallback = new DartExecutor.DartCallback(getAssets(), FlutterInjector.instance().flutterLoader().findAppBundlePath(), callback); + engine.getDartExecutor().executeDartCallback(dartCallback); + } catch (UnsatisfiedLinkError e) { + Log.e(TAG, "Error: " + e.getMessage()); + } + } + + public void receiveData(String data) { + if (methodChannel != null) { + methodChannel.invokeMethod(MoxplatformAndroidPlugin.dataReceivedMethodName, data); + } + } + + @Override + public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + switch (call.method) { + case "getHandler": + result.success(MoxplatformAndroidPlugin.getHandle(this)); + break; + case "getExtraData": + result.success(MoxplatformAndroidPlugin.getExtraData(this)); + break; + case "setNotificationBody": + String body = ((String) ((ArrayList) call.arguments).get(0)); + notificationBody = body; + updateNotificationInfo(); + result.success(true); + break; + case "sendData": + LocalBroadcastManager sendDataManager = LocalBroadcastManager.getInstance(this); + Intent sendDataIntent = new Intent(MoxplatformAndroidPlugin.methodChannelKey); + sendDataIntent.putExtra("data", (String) call.arguments); + sendDataManager.sendBroadcast(sendDataIntent); + result.success(true); + break; + case "stop": + isManuallyStopped = true; + Intent stopIntent = new Intent(this, WatchdogReceiver.class); + boolean aboveS = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S; + PendingIntent pending = PendingIntent.getBroadcast( + getApplicationContext(), + 111, + stopIntent, + PendingIntent.FLAG_CANCEL_CURRENT + // Only enable FLAG_MUTABLE when the Android version is Android S or greater + | (PendingIntent.FLAG_MUTABLE & (aboveS ? PendingIntent.FLAG_MUTABLE : 0))); + AlarmManager stopManager = (AlarmManager) getSystemService(ALARM_SERVICE); + stopManager.cancel(pending); + stopSelf(); + MoxplatformAndroidPlugin.setStartAtBoot(this, false); + result.success(true); + break; + default: + result.notImplemented(); + break; + } + } + + @Override + public void onCreate() { + super.onCreate(); + + createNotificationChannel(); + notificationBody = "Preparing..."; + updateNotificationInfo(); + } + + @Override + public void onDestroy() { + if (!isManuallyStopped) { + enqueue(this); + } else { + setManuallyStopped(this,true); + } + + if (engine != null) { + engine.getServiceControlSurface().detachFromService(); + engine.destroy(); + engine = null; + } + + stopForeground(true); + isRunning.set(false); + + methodChannel = null; + dartCallback = null; + super.onDestroy(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + setManuallyStopped(this,false); + enqueue(this); + runService(); + getLock(getApplicationContext()).acquire(); + + return START_STICKY; + } +} diff --git a/src/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/BootReceiver.java b/src/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/BootReceiver.java new file mode 100644 index 0000000..2e40784 --- /dev/null +++ b/src/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/BootReceiver.java @@ -0,0 +1,19 @@ +package me.polynom.moxplatform_android; + + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import androidx.core.content.ContextCompat; + +import androidx.core.content.ContextCompat; + +public class BootReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (MoxplatformAndroidPlugin.getStartAtBoot(context)) { + ContextCompat.startForegroundService(context, new Intent(context, BackgroundService.class)); + } + } +} \ No newline at end of file diff --git a/src/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/MoxplatformAndroidPlugin.java b/src/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/MoxplatformAndroidPlugin.java new file mode 100644 index 0000000..f24446f --- /dev/null +++ b/src/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/MoxplatformAndroidPlugin.java @@ -0,0 +1,178 @@ +package me.polynom.moxplatform_android; + +import android.app.ActivityManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + +import java.util.ArrayList; +import java.util.List; + +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.service.ServiceAware; +import io.flutter.embedding.engine.plugins.service.ServicePluginBinding; +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 io.flutter.plugin.common.PluginRegistry.Registrar; +import io.flutter.plugin.common.JSONMethodCodec; + +public class MoxplatformAndroidPlugin extends BroadcastReceiver implements FlutterPlugin, MethodCallHandler, ServiceAware { + public static final String entrypointKey = "entrypoint_handle"; + public static final String extraDataKey = "extra_data"; + private static final String autoStartAtBootKey = "auto_start_at_boot"; + public static final String sharedPrefKey = "me.polynom.moxplatform_android"; + private static final String TAG = "moxplatform_android"; + public static final String methodChannelKey = "me.polynom.moxplatform_android"; + public static final String dataReceivedMethodName = "dataReceived"; + + private static final List _instances = new ArrayList<>(); + private BackgroundService service; + private MethodChannel channel; + private Context context; + + public MoxplatformAndroidPlugin() { + _instances.add(this); + } + + @Override + public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { + channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), methodChannelKey); + channel.setMethodCallHandler(this); + context = flutterPluginBinding.getApplicationContext(); + + LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this.context); + localBroadcastManager.registerReceiver(this, new IntentFilter(methodChannelKey)); + + Log.d(TAG, "Attached to engine"); + } + + static void registerWith(Registrar registrar) { + LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(registrar.context()); + final MoxplatformAndroidPlugin plugin = new MoxplatformAndroidPlugin(); + localBroadcastManager.registerReceiver(plugin, new IntentFilter(methodChannelKey)); + + final MethodChannel channel = new MethodChannel(registrar.messenger(), "me.polynom/background_service_android", JSONMethodCodec.INSTANCE); + channel.setMethodCallHandler(plugin); + plugin.channel = channel; + + Log.d(TAG, "Registered against registrar"); + } + + /// Store the entrypoint handle and extra data for the background service. + private void configure(long entrypointHandle, String extraData) { + SharedPreferences prefs = context.getSharedPreferences(sharedPrefKey, Context.MODE_PRIVATE); + prefs.edit() + .putLong(entrypointKey, entrypointHandle) + .putString(extraDataKey, extraData) + .apply(); + } + + public static long getHandle(Context c) { + return c.getSharedPreferences(sharedPrefKey, Context.MODE_PRIVATE).getLong(entrypointKey, 0); + } + + public static String getExtraData(Context c) { + return c.getSharedPreferences(sharedPrefKey, Context.MODE_PRIVATE).getString(extraDataKey, ""); + } + + public static void setStartAtBoot(Context c, boolean value) { + c.getSharedPreferences(sharedPrefKey, Context.MODE_PRIVATE) + .edit() + .putBoolean(extraDataKey, value) + .apply(); + } + public static boolean getStartAtBoot(Context c) { + return c.getSharedPreferences(sharedPrefKey, Context.MODE_PRIVATE).getBoolean(extraDataKey, false); + } + + private boolean isRunning() { + ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + for (ActivityManager.RunningServiceInfo info : manager.getRunningServices(Integer.MAX_VALUE)) { + if (BackgroundService.class.getName().equals(info.service.getClassName())) { + return true; + } + } + + return false; + } + + @Override + public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { + switch (call.method) { + case "configure": + ArrayList args = (ArrayList) call.arguments; + long handle = (long) args.get(0); + String extraData = (String) args.get(1); + + configure(handle, extraData); + result.success(true); + break; + case "isRunning": + result.success(isRunning()); + break; + case "start": + MoxplatformAndroidPlugin.setStartAtBoot(context, true); + BackgroundService.enqueue(context); + Intent intent = new Intent(context, BackgroundService.class); + ContextCompat.startForegroundService(context, intent); + Log.d(TAG, "Service started"); + result.success(true); + break; + case "sendData": + for (MoxplatformAndroidPlugin plugin : _instances) { + if (plugin.service != null) { + plugin.service.receiveData((String) call.arguments); + break; + } + } + result.success(true); + break; + default: + result.notImplemented(); + break; + } + } + + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction() == null) return; + + if (intent.getAction().equalsIgnoreCase(methodChannelKey)) { + String data = intent.getStringExtra("data"); + + if (channel != null) { + channel.invokeMethod(dataReceivedMethodName, data); + } + } + } + + @Override + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + channel.setMethodCallHandler(null); + LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this.context); + localBroadcastManager.unregisterReceiver(this); + + Log.d(TAG, "Detached from engine"); + } + + @Override + public void onAttachedToService(@NonNull ServicePluginBinding binding) { + Log.d(TAG, "Attached to service"); + this.service = (BackgroundService) binding.getService(); + } + + @Override + public void onDetachedFromService() { + Log.d(TAG, "Detached from service"); + this.service = null; + } +} diff --git a/src/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/WatchdogReceiver.java b/src/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/WatchdogReceiver.java new file mode 100644 index 0000000..d582ac9 --- /dev/null +++ b/src/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/WatchdogReceiver.java @@ -0,0 +1,16 @@ +package me.polynom.moxplatform_android; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import androidx.core.content.ContextCompat; + +public class WatchdogReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if(!BackgroundService.isManuallyStopped(context)){ + ContextCompat.startForegroundService(context, new Intent(context, BackgroundService.class)); + } + } +} diff --git a/src/moxplatform_android/android/src/main/kotlin/com/example/my_plugin/MyPluginPlugin.kt b/src/moxplatform_android/android/src/main/kotlin/com/example/my_plugin/MyPluginPlugin.kt deleted file mode 100644 index c0467f5..0000000 --- a/src/moxplatform_android/android/src/main/kotlin/com/example/my_plugin/MyPluginPlugin.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.example.my_plugin - -import android.content.Context -import androidx.annotation.NonNull - -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 - -class MyPluginPlugin : FlutterPlugin, MethodCallHandler { - private lateinit var channel: MethodChannel - private var context: Context? = null - - override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - channel = MethodChannel(flutterPluginBinding.binaryMessenger, "my_plugin_android") - channel.setMethodCallHandler(this) - context = flutterPluginBinding.applicationContext - } - - override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { - if (call.method == "getPlatformName") { - result.success("Android") - } else { - result.notImplemented() - } - } - - override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { - channel.setMethodCallHandler(null) - context = null - } -} \ No newline at end of file diff --git a/src/moxplatform_android/android/src/main/res/drawable-anydpi-v24/ic_service_icon.xml b/src/moxplatform_android/android/src/main/res/drawable-anydpi-v24/ic_service_icon.xml new file mode 100644 index 0000000..b58d199 --- /dev/null +++ b/src/moxplatform_android/android/src/main/res/drawable-anydpi-v24/ic_service_icon.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + diff --git a/src/moxplatform_android/android/src/main/res/drawable-hdpi/ic_service_icon.png b/src/moxplatform_android/android/src/main/res/drawable-hdpi/ic_service_icon.png new file mode 100644 index 0000000..7e1c0f4 Binary files /dev/null and b/src/moxplatform_android/android/src/main/res/drawable-hdpi/ic_service_icon.png differ diff --git a/src/moxplatform_android/android/src/main/res/drawable-mdpi/ic_service_icon.png b/src/moxplatform_android/android/src/main/res/drawable-mdpi/ic_service_icon.png new file mode 100644 index 0000000..76003b6 Binary files /dev/null and b/src/moxplatform_android/android/src/main/res/drawable-mdpi/ic_service_icon.png differ diff --git a/src/moxplatform_android/android/src/main/res/drawable-xhdpi/ic_service_icon.png b/src/moxplatform_android/android/src/main/res/drawable-xhdpi/ic_service_icon.png new file mode 100644 index 0000000..8fbd252 Binary files /dev/null and b/src/moxplatform_android/android/src/main/res/drawable-xhdpi/ic_service_icon.png differ diff --git a/src/moxplatform_android/android/src/main/res/drawable-xxhdpi/ic_service_icon.png b/src/moxplatform_android/android/src/main/res/drawable-xxhdpi/ic_service_icon.png new file mode 100644 index 0000000..2f4065e Binary files /dev/null and b/src/moxplatform_android/android/src/main/res/drawable-xxhdpi/ic_service_icon.png differ diff --git a/src/moxplatform_android/lib/isolate_android.dart b/src/moxplatform_android/lib/isolate_android.dart index f01d49b..b43e35d 100644 --- a/src/moxplatform_android/lib/isolate_android.dart +++ b/src/moxplatform_android/lib/isolate_android.dart @@ -4,59 +4,67 @@ import "dart:ui"; import "package:moxplatform_android/service_android.dart"; import "package:flutter/widgets.dart"; +import "package:flutter/services.dart"; import "package:logging/logging.dart"; -import "package:get_it/get_it.dart"; -import "package:flutter_background_service/flutter_background_service.dart"; -import "package:flutter_background_service_android/flutter_background_service_android.dart"; import "package:moxplatform_platform_interface/src/isolate.dart"; import "package:moxplatform_platform_interface/src/service.dart"; import "package:moxplatform/types.dart"; import "package:moxlib/awaitabledatasender.dart"; +import "package:get_it/get_it.dart"; /// An [AwaitableDataSender] that uses flutter_background_service. class BackgroundServiceDataSender extends AwaitableDataSender { - final FlutterBackgroundService _srv; - - BackgroundServiceDataSender() : _srv = FlutterBackgroundService(), super(); + final MethodChannel _channel; + BackgroundServiceDataSender() : _channel = MethodChannel("me.polynom.moxplatform_android"), super(); @override Future sendDataImpl(DataWrapper data) async { - _srv.invoke("command", data.toJson()); + await _channel.invokeMethod("sendData", jsonEncode(data.toJson())); } } -void onStart(ServiceInstance instance, String extra) { - final data = jsonDecode(extra); - final int entrypointHandle = data["entrypointHandle"]!; +Future androidEntrypoint() async { + print("androidEntrypoint: Called on new FlutterEngine"); + WidgetsFlutterBinding.ensureInitialized(); + + /* + AndroidBackgroundService.channel.setMethodCallHandler((MethodCall call) async { + print(call.method); + });*/ + + final data = jsonDecode(await AndroidBackgroundService.channel.invokeMethod("getExtraData", [])); + final int entrypointHandle = data["genericEntrypoint"]!; final entrypointCallbackHandle = CallbackHandle.fromRawHandle(entrypointHandle); final entrypoint = PluginUtilities.getCallbackFromHandle(entrypointCallbackHandle); final int handleUIEventHandle = data["eventHandle"]!; final handleUIEventCallbackHandle = CallbackHandle.fromRawHandle(handleUIEventHandle); final handleUIEvent = PluginUtilities.getCallbackFromHandle(handleUIEventCallbackHandle); - final srv = AndroidBackgroundService(instance as AndroidServiceInstance); + final srv = AndroidBackgroundService(); GetIt.I.registerSingleton(srv); srv.init( - () async => await entrypoint!(), - (data) async => await handleUIEvent!(data) + entrypoint! as Future Function(), + handleUIEvent! as Future Function(Map? data) ); } /// The Android specific implementation of the [IsolateHandler]. class AndroidIsolateHandler extends IsolateHandler { - final FlutterBackgroundService _srv; final BackgroundServiceDataSender _dataSender; + final MethodChannel _channel; final Logger _log; AndroidIsolateHandler() - : _srv = FlutterBackgroundService(), + : _channel = MethodChannel("me.polynom.moxplatform_android"), _dataSender = BackgroundServiceDataSender(), _log = Logger("AndroidIsolateHandler"), super(); @override - void attach(Future Function(Map? data) handleIsolateEvent) { - _srv.on("event").listen(handleIsolateEvent); + Future attach(Future Function(Map? data) handleIsolateEvent) async { + _channel.setMethodCallHandler((MethodCall call) async { + await handleIsolateEvent(jsonDecode(call.arguments)); + }); } @override @@ -67,27 +75,19 @@ class AndroidIsolateHandler extends IsolateHandler { ) async { _log.finest("Called start"); WidgetsFlutterBinding.ensureInitialized(); - - _srv.on("event").listen(handleIsolateEvent); - await _srv.configure( - iosConfiguration: IosConfiguration( - autoStart: true, - onBackground: (_) => true, - onForeground: (_, __) => true - ), - androidConfiguration: AndroidConfiguration( - extraData: jsonEncode( - { - "entrypointHandle": PluginUtilities.getCallbackHandle(entrypoint)!.toRawHandle(), + + await _channel.invokeMethod("configure", [ + PluginUtilities.getCallbackHandle(androidEntrypoint)!.toRawHandle(), + jsonEncode({ + "genericEntrypoint": PluginUtilities.getCallbackHandle(entrypoint)!.toRawHandle(), "eventHandle": PluginUtilities.getCallbackHandle(handleUIEvent)!.toRawHandle() - } - ), - onStart: onStart, - autoStart: true, - isForegroundMode: true - ) - ); - if (await _srv.startService()) { + }) + ]); + + await attach(handleIsolateEvent); + + final result = await _channel.invokeMethod("start", []); + if (result) { _log.finest("Service successfully started"); } else { _log.severe("Service failed to start"); @@ -97,8 +97,7 @@ class AndroidIsolateHandler extends IsolateHandler { @override Future isRunning() async { WidgetsFlutterBinding.ensureInitialized(); - - return await _srv.isRunning(); + return await _channel.invokeMethod("isRunning", []); } @override diff --git a/src/moxplatform_android/lib/moxplatform_android.dart b/src/moxplatform_android/lib/moxplatform_android.dart index f5323fd..3887c73 100644 --- a/src/moxplatform_android/lib/moxplatform_android.dart +++ b/src/moxplatform_android/lib/moxplatform_android.dart @@ -5,7 +5,7 @@ import "package:moxplatform_platform_interface/moxplatform_platform_interface.da class MoxplatformAndroidPlugin extends MoxplatformInterface { static void registerWith() { - print("========================================================================================"); + print("MoxplatformAndroidPlugin: Registering implementation"); MoxplatformInterface.handler = AndroidIsolateHandler(); MoxplatformInterface.media = AndroidMediaScannerImplementation(); } diff --git a/src/moxplatform_android/lib/moxplatform_android_method_channel.dart b/src/moxplatform_android/lib/moxplatform_android_method_channel.dart new file mode 100644 index 0000000..106dc40 --- /dev/null +++ b/src/moxplatform_android/lib/moxplatform_android_method_channel.dart @@ -0,0 +1,17 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +import 'moxplatform_android_platform_interface.dart'; + +/// An implementation of [MoxplatformAndroidPlatform] that uses method channels. +class MethodChannelMoxplatformAndroid extends MoxplatformAndroidPlatform { + /// The method channel used to interact with the native platform. + @visibleForTesting + final methodChannel = const MethodChannel('moxplatform_android'); + + @override + Future getPlatformVersion() async { + final version = await methodChannel.invokeMethod('getPlatformVersion'); + return version; + } +} diff --git a/src/moxplatform_android/lib/moxplatform_android_platform_interface.dart b/src/moxplatform_android/lib/moxplatform_android_platform_interface.dart new file mode 100644 index 0000000..1123e31 --- /dev/null +++ b/src/moxplatform_android/lib/moxplatform_android_platform_interface.dart @@ -0,0 +1,29 @@ +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import 'moxplatform_android_method_channel.dart'; + +abstract class MoxplatformAndroidPlatform extends PlatformInterface { + /// Constructs a MoxplatformAndroidPlatform. + MoxplatformAndroidPlatform() : super(token: _token); + + static final Object _token = Object(); + + static MoxplatformAndroidPlatform _instance = MethodChannelMoxplatformAndroid(); + + /// The default instance of [MoxplatformAndroidPlatform] to use. + /// + /// Defaults to [MethodChannelMoxplatformAndroid]. + static MoxplatformAndroidPlatform get instance => _instance; + + /// Platform-specific implementations should set this with their own + /// platform-specific class that extends [MoxplatformAndroidPlatform] when + /// they register themselves. + static set instance(MoxplatformAndroidPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + Future getPlatformVersion() { + throw UnimplementedError('platformVersion() has not been implemented.'); + } +} diff --git a/src/moxplatform_android/lib/service_android.dart b/src/moxplatform_android/lib/service_android.dart index eb741fc..5faf58a 100644 --- a/src/moxplatform_android/lib/service_android.dart +++ b/src/moxplatform_android/lib/service_android.dart @@ -1,25 +1,30 @@ +import "dart:convert"; import "dart:ui"; import "package:moxplatform_platform_interface/src/service.dart"; import "package:moxplatform/types.dart"; import "package:moxlib/awaitabledatasender.dart"; import "package:flutter/material.dart"; +import "package:flutter/services.dart"; import "package:logging/logging.dart"; import "package:uuid/uuid.dart"; -import "package:flutter_background_service_android/flutter_background_service_android.dart"; +import "package:meta/meta.dart"; class AndroidBackgroundService extends BackgroundService { + @internal + static const MethodChannel channel = MethodChannel("me.polynom.moxplatform_android_bg"); final Logger _log; - final AndroidServiceInstance _srv; - AndroidBackgroundService(AndroidServiceInstance srv) - : _srv = srv, - _log = Logger("AndroidBackgroundService"), + AndroidBackgroundService() + : _log = Logger("AndroidBackgroundService"), super(); @override void setNotification(String title, String body) { - _srv.setForegroundNotificationInfo(title: title, content: body); + channel.invokeMethod( + "setNotificationBody", + [ body ] + ); } @override @@ -30,13 +35,13 @@ class AndroidBackgroundService extends BackgroundService { ); // NOTE: *S*erver to *F*oreground _log.fine("S2F: ${data.toJson().toString()}"); - _srv.invoke("event", data.toJson()); + channel.invokeMethod("sendData", jsonEncode(data.toJson())); } @override void init( Future Function() entrypoint, - void Function(Map? data) handleEvent + Future Function(Map? data) handleEvent ) { WidgetsFlutterBinding.ensureInitialized(); @@ -44,7 +49,11 @@ class AndroidBackgroundService extends BackgroundService { // we can use path_provider, notifications, ... DartPluginRegistrant.ensureInitialized(); - _srv.on("command").listen(handleEvent); + // Register the event handler + channel.setMethodCallHandler((MethodCall call) async { + await handleEvent(jsonDecode(call.arguments)); + }); + setNotification("Moxxy", "Preparing..."); _log.finest("Running..."); diff --git a/src/moxplatform_android/pubspec.yaml b/src/moxplatform_android/pubspec.yaml index 0f8b8b0..34fc2a8 100644 --- a/src/moxplatform_android/pubspec.yaml +++ b/src/moxplatform_android/pubspec.yaml @@ -12,24 +12,25 @@ flutter: implements: moxplatform platforms: android: - package: com.example.my_plugin - pluginClass: MyPluginPlugin + package: me.polynom.moxplatform_android + pluginClass: MoxplatformAndroidPlugin dartPluginClass: MoxplatformAndroidPlugin dependencies: flutter: sdk: flutter moxplatform_platform_interface: - git: - url: https://codeberg.org/moxxy/moxplatform.git - rev: c19ae302bb8d2e2bd8db220cc6a9a488c33e05d1 - path: src/moxplatform_platform_interface + #git: + # url: https://codeberg.org/moxxy/moxplatform.git + # rev: c19ae302bb8d2e2bd8db220cc6a9a488c33e05d1 + # path: src/moxplatform_platform_interface + path: ../moxplatform_platform_interface moxplatform: - git: - url: https://codeberg.org/moxxy/moxplatform.git - rev: c19ae302bb8d2e2bd8db220cc6a9a488c33e05d1 - path: src/moxplatform - + #git: + # url: https://codeberg.org/moxxy/moxplatform.git + # rev: c19ae302bb8d2e2bd8db220cc6a9a488c33e05d1 + # path: src/moxplatform + path: ../moxplatform moxlib: git: url: https://codeberg.org/moxxy/moxlib.git @@ -37,12 +38,6 @@ dependencies: logging: 1.0.2 get_it: 7.2.0 uuid: 3.0.5 - flutter_background_service: - git: - url: https://github.com/PapaTutuWawa/flutter_background_service.git - rev: moxxy/additional-data - path: packages/flutter_background_service - media_scanner: 2.0.0 dev_dependencies: diff --git a/src/moxplatform_android/test/moxplatform_android_method_channel_test.dart b/src/moxplatform_android/test/moxplatform_android_method_channel_test.dart new file mode 100644 index 0000000..ee9365a --- /dev/null +++ b/src/moxplatform_android/test/moxplatform_android_method_channel_test.dart @@ -0,0 +1,24 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:moxplatform_android/moxplatform_android_method_channel.dart'; + +void main() { + MethodChannelMoxplatformAndroid platform = MethodChannelMoxplatformAndroid(); + const MethodChannel channel = MethodChannel('moxplatform_android'); + + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + return '42'; + }); + }); + + tearDown(() { + channel.setMockMethodCallHandler(null); + }); + + test('getPlatformVersion', () async { + expect(await platform.getPlatformVersion(), '42'); + }); +} diff --git a/src/moxplatform_platform_interface/lib/src/isolate.dart b/src/moxplatform_platform_interface/lib/src/isolate.dart index 67ad6f9..7818615 100644 --- a/src/moxplatform_platform_interface/lib/src/isolate.dart +++ b/src/moxplatform_platform_interface/lib/src/isolate.dart @@ -15,7 +15,7 @@ abstract class IsolateHandler { ); /// Make sure that the UI event handler is registered without starting the isolate. - void attach( + Future attach( Future Function(Map? data) handleIsolateEvent ); diff --git a/src/moxplatform_platform_interface/lib/src/isolate_stub.dart b/src/moxplatform_platform_interface/lib/src/isolate_stub.dart index 9c7b533..da4cfcc 100644 --- a/src/moxplatform_platform_interface/lib/src/isolate_stub.dart +++ b/src/moxplatform_platform_interface/lib/src/isolate_stub.dart @@ -14,9 +14,9 @@ class StubIsolateHandler extends IsolateHandler { StubIsolateHandler() : _sender = StubDataSender(); @override - void attach( + Future attach( Future Function(Map? data) handleIsolateEvent - ) { + ) async { print("STUB ATTACHED!!!!!!"); } diff --git a/src/moxplatform_platform_interface/lib/src/service.dart b/src/moxplatform_platform_interface/lib/src/service.dart index 9a76a26..90f6717 100644 --- a/src/moxplatform_platform_interface/lib/src/service.dart +++ b/src/moxplatform_platform_interface/lib/src/service.dart @@ -12,6 +12,6 @@ abstract class BackgroundService { /// data. void init( Future Function() entrypoint, - void Function(Map? data) handleEvent + Future Function(Map? data) handleEvent ); } diff --git a/src/moxplatform_platform_interface/pubspec.yaml b/src/moxplatform_platform_interface/pubspec.yaml index 51b1bba..397da34 100644 --- a/src/moxplatform_platform_interface/pubspec.yaml +++ b/src/moxplatform_platform_interface/pubspec.yaml @@ -11,10 +11,11 @@ dependencies: sdk: flutter plugin_platform_interface: ^2.1.0 moxplatform: - git: - url: https://codeberg.org/moxxy/moxplatform.git - rev: c19ae302bb8d2e2bd8db220cc6a9a488c33e05d1 - path: src/moxplatform + #git: + # url: https://codeberg.org/moxxy/moxplatform.git + # rev: c19ae302bb8d2e2bd8db220cc6a9a488c33e05d1 + # path: src/moxplatform + path: ../moxplatform moxlib: git: