diff --git a/.github/labeler.yml b/.github/labeler.yml
index 44c32d452..234f8a80b 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -47,6 +47,8 @@
- packages/shared_preferences/**/*
'p: share_plus':
- packages/share_plus/**/*
+'p: tizen_app_control':
+ - packages/tizen_app_control/**/*
'p: url_launcher':
- packages/url_launcher/**/*
'p: video_player':
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 05d75e836..61630cd94 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -57,6 +57,12 @@ jobs:
NativeToolchain-Gcc-9.2 \
WEARABLE-4.0-NativeAppDevelopment \
WEARABLE-5.5-NativeAppDevelopment
+ - name: Create a Tizen certificate profile
+ if: ${{ env.HAS_CHANGED_PACKAGES == 'true' }}
+ run: |
+ export PATH=$PATH:$HOME/tizen-studio/tools/ide/bin
+ tizen certificate -a platform -p platform -f platform
+ tizen security-profiles add -n platform -a $HOME/tizen-studio-data/keystore/author/platform.p12 -p platform
- name: Install flutter-tizen
if: ${{ env.HAS_CHANGED_PACKAGES == 'true' }}
run: |
diff --git a/README.md b/README.md
index e76002da3..ee8e43b20 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# plugins
-[](https://github.com/flutter-tizen/plugins/actions)
+[](https://github.com/flutter-tizen/plugins/actions/workflows/build.yml)
This repo contains Flutter plugins maintained by the flutter-tizen team. We're in process of adding Tizen platform support to existing first and third-party plugins on [pub.dev](https://pub.dev) based on their popularity. If the plugin you're looking for isn't implemented for Tizen yet, consider filing an [issue](../../issues) or creating a package by yourself. (We welcome your pull requests!)
@@ -31,6 +31,7 @@ The _"non-endorsed"_ status means that the plugin is not endorsed by the origina
| [**sensors_plus_tizen**](packages/sensors_plus) | [sensors_plus](https://github.com/fluttercommunity/plus_plugins/tree/main/packages/sensors_plus) (1st-party) | [](https://pub.dev/packages/sensors_plus_tizen) | No |
| [**share_plus_tizen**](packages/share_plus) | [share_plus](https://github.com/fluttercommunity/plus_plugins/tree/main/packages/share_plus) (1st-party) | [](https://pub.dev/packages/share_plus_tizen) | No |
| [**shared_preferences_tizen**](packages/shared_preferences) | [shared_preferences](https://github.com/flutter/plugins/tree/master/packages/shared_preferences) (1st-party) | [](https://pub.dev/packages/shared_preferences_tizen) | No |
+| [**tizen_app_control**](packages/tizen_app_control) | (Tizen-only) | [](https://pub.dev/packages/tizen_app_control) | N/A |
| [**url_launcher_tizen**](packages/url_launcher) | [url_launcher](https://github.com/flutter/plugins/tree/master/packages/url_launcher) (1st-party) | [](https://pub.dev/packages/url_launcher_tizen) | No |
| [**video_player_tizen**](packages/video_player) | [video_player](https://github.com/flutter/plugins/tree/master/packages/video_player) (1st-party) | [](https://pub.dev/packages/video_player_tizen) | No |
| [**wakelock_tizen**](packages/wakelock) | [wakelock](https://github.com/creativecreatorormaybenot/wakelock) (3rd-party) | [](https://pub.dev/packages/wakelock_tizen) | No |
@@ -72,6 +73,7 @@ The following packages are deprecated.
| [**sensors_plus_tizen**](packages/sensors_plus) | ✔️ | ✔️ | ❌ | ❌ | No sensor hardware |
| [**share_plus_tizen**](packages/share_plus) | ⚠️ | ⚠️ | ❌ | ❌ | No SMS or e-mail app |
| [**shared_preferences_tizen**](packages/shared_preferences) | ✔️ | ✔️ | ✔️ | ✔️ |
+| [**tizen_app_control**](packages/tizen_app_control) | ✔️ | ✔️ | ✔️ | ✔️ |
| [**url_launcher_tizen**](packages/url_launcher) | ✔️ | ❌ | ✔️ | ❌ | No browser app |
| [**video_player_tizen**](packages/video_player) | ✔️ | ✔️ | ✔️ | ❌ | TV emulator issue |
| [**wakelock_tizen**](packages/wakelock) | ✔️ | ✔️ | ❌ | ❌ | Cannot override system display setting |
diff --git a/packages/tizen_app_control/.gitignore b/packages/tizen_app_control/.gitignore
new file mode 100644
index 000000000..a247422ef
--- /dev/null
+++ b/packages/tizen_app_control/.gitignore
@@ -0,0 +1,75 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+
+# 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/
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+build/
+
+# Android related
+**/android/**/gradle-wrapper.jar
+**/android/.gradle
+**/android/captures/
+**/android/gradlew
+**/android/gradlew.bat
+**/android/local.properties
+**/android/**/GeneratedPluginRegistrant.java
+
+# iOS/XCode related
+**/ios/**/*.mode1v3
+**/ios/**/*.mode2v3
+**/ios/**/*.moved-aside
+**/ios/**/*.pbxuser
+**/ios/**/*.perspectivev3
+**/ios/**/*sync/
+**/ios/**/.sconsign.dblite
+**/ios/**/.tags*
+**/ios/**/.vagrant/
+**/ios/**/DerivedData/
+**/ios/**/Icon?
+**/ios/**/Pods/
+**/ios/**/.symlinks/
+**/ios/**/profile
+**/ios/**/xcuserdata
+**/ios/.generated/
+**/ios/Flutter/App.framework
+**/ios/Flutter/Flutter.framework
+**/ios/Flutter/Flutter.podspec
+**/ios/Flutter/Generated.xcconfig
+**/ios/Flutter/ephemeral
+**/ios/Flutter/app.flx
+**/ios/Flutter/app.zip
+**/ios/Flutter/flutter_assets/
+**/ios/Flutter/flutter_export_environment.sh
+**/ios/ServiceDefinitions.json
+**/ios/Runner/GeneratedPluginRegistrant.*
+
+# Exceptions to above rules.
+!**/ios/**/default.mode1v3
+!**/ios/**/default.mode2v3
+!**/ios/**/default.pbxuser
+!**/ios/**/default.perspectivev3
diff --git a/packages/tizen_app_control/CHANGELOG.md b/packages/tizen_app_control/CHANGELOG.md
new file mode 100644
index 000000000..607323422
--- /dev/null
+++ b/packages/tizen_app_control/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 0.1.0
+
+* Initial release.
diff --git a/packages/tizen_app_control/LICENSE b/packages/tizen_app_control/LICENSE
new file mode 100644
index 000000000..b8fc05698
--- /dev/null
+++ b/packages/tizen_app_control/LICENSE
@@ -0,0 +1,25 @@
+Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of the copyright holder nor the names of the
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/packages/tizen_app_control/README.md b/packages/tizen_app_control/README.md
new file mode 100644
index 000000000..a335df0f2
--- /dev/null
+++ b/packages/tizen_app_control/README.md
@@ -0,0 +1,69 @@
+# tizen_app_control
+
+Tizen application control APIs. Used for launching and terminating applications on a Tizen device.
+
+## Usage
+
+To use this package, add `tizen_app_control` as a dependency in your `pubspec.yaml` file.
+
+```yaml
+dependencies:
+ tizen_app_control: ^0.1.0
+```
+
+### Sending a launch request
+
+To send an explicit launch request, create an `AppControl` instance with an application ID as an argument.
+
+```dart
+import 'package:tizen_app_control/app_control.dart';
+
+var request = AppControl(appId: 'com.example.app_id');
+await request.sendLaunchRequest();
+```
+
+To send an implicit launch request, create an `AppControl` instance and specify necessary conditions, such as operation, URI, and MIME type. For example, if you want to open an image file with an image viewer app on your device,
+
+```dart
+import 'package:tizen_app_control/app_control.dart';
+
+var request = AppControl(
+ operation: 'http://tizen.org/appcontrol/operation/view',
+ uri: 'file:///image_file_path',
+ mime: 'image/*',
+);
+await request.sendLaunchRequest();
+```
+
+For detailed information on Tizen application controls, see [Tizen Docs: Application Controls](https://docs.tizen.org/application/native/guides/app-management/app-controls). For a list of common operation types and examples, see [Tizen Docs: Common Application Controls](https://docs.tizen.org/application/native/guides/app-management/common-appcontrols). Operation and data constants, such as `http://tizen.org/appcontrol/operation/view`, are defined in [the native API references](https://docs.tizen.org/application/native/api/wearable/latest/group__CAPI__APP__CONTROL__MODULE.html).
+
+### Receiving a launch request
+
+You can subscribe to incoming application controls using `AppControl.onAppControl`.
+
+```dart
+import 'package:tizen_app_control/app_control.dart';
+
+var subscription = AppControl.onAppControl.listen((request) async {
+ if (request.shouldReply) {
+ var reply = AppControl();
+ await request.reply(reply, AppControlReplyResult.succeeded);
+ }
+});
+...
+await subscription.cancel();
+```
+
+## Required privileges
+
+Privileges may be required to perform operations requested by your app. Add required privileges in `tizen-manifest.xml` of your application.
+
+```xml
+
+ http://tizen.org/privilege/appmanager.launch
+
+ http://tizen.org/privilege/appmanager.kill.bgapp
+ http://tizen.org/privilege/call
+ http://tizen.org/privilege/download
+
+```
diff --git a/packages/tizen_app_control/example/.gitignore b/packages/tizen_app_control/example/.gitignore
new file mode 100644
index 000000000..0fa6b675c
--- /dev/null
+++ b/packages/tizen_app_control/example/.gitignore
@@ -0,0 +1,46 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+
+# 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/packages/tizen_app_control/example/README.md b/packages/tizen_app_control/example/README.md
new file mode 100644
index 000000000..755f6a43d
--- /dev/null
+++ b/packages/tizen_app_control/example/README.md
@@ -0,0 +1,7 @@
+# tizen_app_control_example
+
+Demonstrates how to use the tizen_app_control plugin.
+
+## Getting Started
+
+To run this app on your Tizen device, use [flutter-tizen](https://github.com/flutter-tizen/flutter-tizen).
diff --git a/packages/tizen_app_control/example/integration_test/tizen_app_control_test.dart b/packages/tizen_app_control/example/integration_test/tizen_app_control_test.dart
new file mode 100644
index 000000000..23592aee3
--- /dev/null
+++ b/packages/tizen_app_control/example/integration_test/tizen_app_control_test.dart
@@ -0,0 +1,131 @@
+// Copyright 2021 Samsung Electronics Co., Ltd. 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:async';
+
+import 'package:flutter/services.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:integration_test/integration_test.dart';
+import 'package:tizen_app_control/app_control.dart';
+import 'package:tizen_app_control/app_manager.dart';
+
+const String kAppId = 'org.tizen.tizen_app_control_example';
+const String kServiceAppId = 'org.tizen.tizen_app_control_example_service';
+const Timeout kTimeout = Timeout(Duration(seconds: 10));
+
+@pragma('vm:entry-point')
+void main() {
+ IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+ testWidgets('Can receive request from platform', (WidgetTester _) async {
+ // The very first message is a launch request from the platform.
+ final ReceivedAppControl received = await AppControl.onAppControl.first;
+ expect(received.appId, kAppId);
+ expect(received.operation, 'http://tizen.org/appcontrol/operation/default');
+ }, timeout: kTimeout);
+
+ testWidgets('Can send and receive request', (WidgetTester _) async {
+ // Send a request to this app (the test runner itself).
+ final AppControl request = AppControl(
+ appId: kAppId,
+ operation: 'operation_1',
+ );
+ await request.sendLaunchRequest();
+
+ final ReceivedAppControl received = await AppControl.onAppControl.first;
+ expect(received.appId, kAppId);
+ expect(received.operation, 'operation_1');
+ expect(received.uri, isNull);
+ expect(received.mime, isNull);
+ expect(received.category, isNull);
+ expect(received.launchMode, LaunchMode.single);
+ expect(received.extraData, isEmpty);
+ expect(received.shouldReply, isFalse);
+ }, timeout: kTimeout);
+
+ testWidgets('Omit invalid extra data', (WidgetTester _) async {
+ final AppControl request = AppControl(
+ appId: kAppId,
+ extraData: {
+ 'STRING_DATA': 'string',
+ 'STRING_LIST_DATA': ['string', 'list'],
+ 'INTEGER_DATA': 1,
+ },
+ );
+ await request.sendLaunchRequest();
+
+ final ReceivedAppControl received = await AppControl.onAppControl.first;
+ expect(received.extraData.length, 2);
+ expect(received.extraData['STRING_DATA'], 'string');
+ expect(received.extraData['STRING_LIST_DATA'], isNotEmpty);
+ expect(received.extraData['STRING_LIST_DATA'][0], 'string');
+ }, timeout: kTimeout);
+
+ testWidgets('Can send and receive reply', (WidgetTester _) async {
+ // This time, the request is sent to the service app instead of the test
+ // runner, because the platform doesn't allow sending a reply back when
+ // caller = callee.
+ final AppControl request = AppControl(
+ appId: kServiceAppId,
+ operation: 'operation_2',
+ );
+ await request.sendLaunchRequest(
+ replyCallback: (
+ AppControl request,
+ AppControl reply,
+ AppControlReplyResult result,
+ ) {
+ expect(result, AppControlReplyResult.canceled);
+ expect(reply.extraData['STRING_DATA'], 'string');
+ },
+ );
+ }, timeout: kTimeout);
+
+ testWidgets('Cannot find target applications', (WidgetTester _) async {
+ final AppControl request1 = AppControl(appId: 'unknown_app');
+ expect(
+ request1.sendLaunchRequest,
+ throwsA(isInstanceOf()),
+ );
+
+ final AppControl request2 = AppControl(operation: 'unknown_operation');
+ expect(
+ request2.sendLaunchRequest,
+ throwsA(isInstanceOf()),
+ );
+ }, timeout: kTimeout);
+
+ testWidgets('Can terminate service application', (WidgetTester _) async {
+ expect(AppManager.isRunning(kServiceAppId), isFalse);
+
+ final AppControl request = AppControl(appId: kServiceAppId);
+ await request.sendLaunchRequest();
+ await Future.delayed(const Duration(seconds: 1));
+ expect(AppManager.isRunning(kServiceAppId), isTrue);
+
+ AppManager.terminateBackgroundApplication(kServiceAppId);
+ await Future.delayed(const Duration(seconds: 1));
+ expect(AppManager.isRunning(kServiceAppId), isFalse);
+ }, timeout: kTimeout);
+}
+
+@pragma('vm:entry-point')
+void serviceMain() {
+ WidgetsFlutterBinding.ensureInitialized();
+
+ AppControl.onAppControl.listen((ReceivedAppControl request) async {
+ if (request.shouldReply) {
+ final AppControl reply = AppControl(
+ extraData: {'STRING_DATA': 'string'},
+ );
+ await request.reply(reply, AppControlReplyResult.canceled);
+ await SystemNavigator.pop();
+ }
+ });
+
+ Future.delayed(kTimeout.duration!).whenComplete(() async {
+ await SystemNavigator.pop();
+ });
+}
diff --git a/packages/tizen_app_control/example/lib/main.dart b/packages/tizen_app_control/example/lib/main.dart
new file mode 100644
index 000000000..fee1f6b5f
--- /dev/null
+++ b/packages/tizen_app_control/example/lib/main.dart
@@ -0,0 +1,198 @@
+// Copyright 2021 Samsung Electronics Co., Ltd. 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:async';
+
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:messageport_tizen/messageport_tizen.dart';
+import 'package:tizen_app_control/app_control.dart';
+import 'package:tizen_app_control/app_manager.dart';
+
+const String _kAppId = 'org.tizen.tizen_app_control_example';
+const String _kServiceAppId = 'org.tizen.tizen_app_control_example_service';
+const String _kPortName = 'service_port';
+
+/// The main entry point for the UI app.
+void main() {
+ runApp(const MyApp());
+}
+
+/// The main entry point for the service app.
+@pragma('vm:entry-point')
+void serviceMain() {
+ // This call is required to use platform channels.
+ WidgetsFlutterBinding.ensureInitialized();
+
+ // Listen for incoming AppControls.
+ final StreamSubscription appControlListener =
+ AppControl.onAppControl.listen((ReceivedAppControl request) async {
+ if (request.shouldReply) {
+ final AppControl reply = AppControl();
+ await request.reply(reply, AppControlReplyResult.succeeded);
+ }
+ });
+
+ // Connect to the UI app and send messages.
+ // An exception will be thrown if the UI app is not running.
+ TizenMessagePort.connectToRemotePort(_kAppId, _kPortName)
+ .then((RemotePort remotePort) async {
+ while (true) {
+ if (await remotePort.check()) {
+ await remotePort.send(null);
+ } else {
+ break;
+ }
+ await Future.delayed(const Duration(seconds: 1));
+ }
+ }).whenComplete(() async {
+ await appControlListener.cancel();
+ await SystemNavigator.pop();
+ });
+}
+
+/// The main UI app widget.
+class MyApp extends StatefulWidget {
+ /// The main UI app widget.
+ const MyApp({Key? key}) : super(key: key);
+
+ @override
+ _MyAppState createState() => _MyAppState();
+}
+
+class _MyAppState extends State {
+ final GlobalKey _messengerKey =
+ GlobalKey();
+ LocalPort? _localPort;
+ int _messagesCount = 0;
+ bool _isServiceStarted = false;
+
+ @override
+ void initState() {
+ super.initState();
+
+ // Open a message port to receive messages from the service app.
+ TizenMessagePort.createLocalPort(_kPortName).then((LocalPort value) {
+ _localPort = value;
+ _localPort?.register((dynamic message, [RemotePort? remotePort]) {
+ setState(() {
+ _messagesCount++;
+ });
+ });
+ });
+ }
+
+ @override
+ void dispose() {
+ super.dispose();
+
+ _localPort?.unregister();
+ }
+
+ Future _sendSms() async {
+ final AppControl request = AppControl(
+ operation: 'http://tizen.org/appcontrol/operation/share_text',
+ uri: 'sms:',
+ launchMode: LaunchMode.group,
+ extraData: {
+ 'http://tizen.org/appcontrol/data/text': 'Some text',
+ },
+ );
+ await request.sendLaunchRequest();
+ }
+
+ Future _pickImage() async {
+ final AppControl request = AppControl(
+ operation: 'http://tizen.org/appcontrol/operation/pick',
+ mime: 'image/*',
+ launchMode: LaunchMode.group,
+ );
+ await request.sendLaunchRequest(
+ replyCallback: (
+ AppControl request,
+ AppControl reply,
+ AppControlReplyResult result,
+ ) {
+ const String kAppControlDataSelected =
+ 'http://tizen.org/appcontrol/data/selected';
+ String? imagePath;
+ if (result == AppControlReplyResult.succeeded &&
+ reply.extraData.containsKey(kAppControlDataSelected)) {
+ imagePath = reply.extraData[kAppControlDataSelected][0] as String;
+ }
+ _messengerKey.currentState!.showSnackBar(
+ SnackBar(content: Text(imagePath ?? 'No image selected.')),
+ );
+ },
+ );
+ }
+
+ Future _launchService() async {
+ final AppControl request = AppControl(appId: _kServiceAppId);
+ await request.sendLaunchRequest(
+ replyCallback: (
+ AppControl request,
+ AppControl reply,
+ AppControlReplyResult result,
+ ) {
+ if (result == AppControlReplyResult.succeeded) {
+ setState(() {
+ _isServiceStarted = true;
+ });
+ } else {
+ _messengerKey.currentState!.showSnackBar(
+ const SnackBar(content: Text('Launch failed.')),
+ );
+ }
+ },
+ );
+ }
+
+ Future _terminateService() async {
+ AppManager.terminateBackgroundApplication(_kServiceAppId);
+ setState(() {
+ _isServiceStarted = AppManager.isRunning(_kServiceAppId);
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ scaffoldMessengerKey: _messengerKey,
+ home: Scaffold(
+ appBar: AppBar(title: const Text('Tizen App Control Example')),
+ body: Center(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ ElevatedButton(
+ onPressed: _sendSms,
+ child: const Text('Send SMS'),
+ ),
+ const SizedBox(height: 10),
+ ElevatedButton(
+ onPressed: _pickImage,
+ child: const Text('Pick image'),
+ ),
+ const SizedBox(height: 10),
+ if (_isServiceStarted)
+ ElevatedButton(
+ onPressed: _terminateService,
+ style: ElevatedButton.styleFrom(primary: Colors.redAccent),
+ child: const Text('Terminate service'),
+ )
+ else
+ ElevatedButton(
+ onPressed: _launchService,
+ child: const Text('Launch service'),
+ ),
+ const SizedBox(height: 10),
+ Text('Received messages: $_messagesCount'),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/packages/tizen_app_control/example/pubspec.yaml b/packages/tizen_app_control/example/pubspec.yaml
new file mode 100644
index 000000000..338154a2d
--- /dev/null
+++ b/packages/tizen_app_control/example/pubspec.yaml
@@ -0,0 +1,23 @@
+name: tizen_app_control_example
+description: Demonstrates how to use the tizen_app_control plugin.
+publish_to: "none"
+
+dependencies:
+ flutter:
+ sdk: flutter
+ messageport_tizen: ^0.1.0
+ tizen_app_control:
+ path: ../
+
+dev_dependencies:
+ flutter_driver:
+ sdk: flutter
+ flutter_test:
+ sdk: flutter
+ integration_test:
+ sdk: flutter
+ integration_test_tizen:
+ path: ../../integration_test/
+
+environment:
+ sdk: ">=2.13.0 <3.0.0"
diff --git a/packages/tizen_app_control/example/test_driver/integration_test.dart b/packages/tizen_app_control/example/test_driver/integration_test.dart
new file mode 100644
index 000000000..b38629cca
--- /dev/null
+++ b/packages/tizen_app_control/example/test_driver/integration_test.dart
@@ -0,0 +1,3 @@
+import 'package:integration_test/integration_test_driver.dart';
+
+Future main() => integrationDriver();
diff --git a/packages/tizen_app_control/example/tizen/.gitignore b/packages/tizen_app_control/example/tizen/.gitignore
new file mode 100644
index 000000000..f68e1be09
--- /dev/null
+++ b/packages/tizen_app_control/example/tizen/.gitignore
@@ -0,0 +1 @@
+flutter/
diff --git a/packages/tizen_app_control/example/tizen/service/.exportMap b/packages/tizen_app_control/example/tizen/service/.exportMap
new file mode 100644
index 000000000..3b97a4f3b
--- /dev/null
+++ b/packages/tizen_app_control/example/tizen/service/.exportMap
@@ -0,0 +1,5 @@
+{
+ global: main;
+ _IO_*;
+ local: *;
+};
diff --git a/packages/tizen_app_control/example/tizen/service/.gitignore b/packages/tizen_app_control/example/tizen/service/.gitignore
new file mode 100644
index 000000000..660cb0f67
--- /dev/null
+++ b/packages/tizen_app_control/example/tizen/service/.gitignore
@@ -0,0 +1,8 @@
+lib/*.so
+res/flutter_assets/
+res/icudtl.dat
+.cproject
+.sign
+crash-info/
+Debug/
+Release/
diff --git a/packages/tizen_app_control/example/tizen/service/inc/runner.h b/packages/tizen_app_control/example/tizen/service/inc/runner.h
new file mode 100644
index 000000000..a2d45b6ee
--- /dev/null
+++ b/packages/tizen_app_control/example/tizen/service/inc/runner.h
@@ -0,0 +1,6 @@
+#ifndef __RUNNER_H__
+#define __RUNNER_H__
+
+#include
+
+#endif /* __RUNNER_H__ */
diff --git a/packages/tizen_app_control/example/tizen/service/project_def.prop b/packages/tizen_app_control/example/tizen/service/project_def.prop
new file mode 100644
index 000000000..b72765c37
--- /dev/null
+++ b/packages/tizen_app_control/example/tizen/service/project_def.prop
@@ -0,0 +1,30 @@
+# See https://docs.tizen.org/application/tizen-studio/native-tools/project-conversion
+# for details.
+
+APPNAME = runner_service
+type = app
+profile = common-4.0
+
+# Source files
+USER_SRCS += src/runner.cc
+
+# User defines
+USER_DEFS =
+USER_UNDEFS =
+USER_CPP_DEFS = TIZEN_DEPRECATION DEPRECATION_WARNING
+USER_CPP_UNDEFS =
+
+# Compiler/linker flags
+USER_CFLAGS_MISC =
+USER_CPPFLAGS_MISC = -c -fmessage-length=0
+USER_LFLAGS = -Wl,-E
+
+# Libraries and objects
+USER_LIB_DIRS = lib
+USER_LIBS =
+USER_OBJS =
+
+# User includes
+USER_INC_DIRS = inc ../flutter src
+USER_INC_FILES =
+USER_CPP_INC_FILES =
diff --git a/packages/tizen_app_control/example/tizen/service/shared/res/ic_launcher.png b/packages/tizen_app_control/example/tizen/service/shared/res/ic_launcher.png
new file mode 100644
index 000000000..4d6372eeb
Binary files /dev/null and b/packages/tizen_app_control/example/tizen/service/shared/res/ic_launcher.png differ
diff --git a/packages/tizen_app_control/example/tizen/service/src/runner.cc b/packages/tizen_app_control/example/tizen/service/src/runner.cc
new file mode 100644
index 000000000..b344cf215
--- /dev/null
+++ b/packages/tizen_app_control/example/tizen/service/src/runner.cc
@@ -0,0 +1,19 @@
+#include "runner.h"
+
+#include "generated_plugin_registrant.h"
+
+class App : public FlutterServiceApp {
+ public:
+ bool OnCreate() {
+ if (FlutterServiceApp::OnCreate()) {
+ RegisterPlugins(this);
+ }
+ return IsRunning();
+ }
+};
+
+int main(int argc, char *argv[]) {
+ App app;
+ app.SetDartEntrypoint("serviceMain");
+ return app.Run(argc, argv);
+}
diff --git a/packages/tizen_app_control/example/tizen/service/tizen-manifest.xml b/packages/tizen_app_control/example/tizen/service/tizen-manifest.xml
new file mode 100644
index 000000000..bd42f8811
--- /dev/null
+++ b/packages/tizen_app_control/example/tizen/service/tizen-manifest.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+ ic_launcher.png
+
+
+
+
diff --git a/packages/tizen_app_control/example/tizen/ui/.exportMap b/packages/tizen_app_control/example/tizen/ui/.exportMap
new file mode 100644
index 000000000..3b97a4f3b
--- /dev/null
+++ b/packages/tizen_app_control/example/tizen/ui/.exportMap
@@ -0,0 +1,5 @@
+{
+ global: main;
+ _IO_*;
+ local: *;
+};
diff --git a/packages/tizen_app_control/example/tizen/ui/.gitignore b/packages/tizen_app_control/example/tizen/ui/.gitignore
new file mode 100644
index 000000000..660cb0f67
--- /dev/null
+++ b/packages/tizen_app_control/example/tizen/ui/.gitignore
@@ -0,0 +1,8 @@
+lib/*.so
+res/flutter_assets/
+res/icudtl.dat
+.cproject
+.sign
+crash-info/
+Debug/
+Release/
diff --git a/packages/tizen_app_control/example/tizen/ui/inc/runner.h b/packages/tizen_app_control/example/tizen/ui/inc/runner.h
new file mode 100644
index 000000000..a2d45b6ee
--- /dev/null
+++ b/packages/tizen_app_control/example/tizen/ui/inc/runner.h
@@ -0,0 +1,6 @@
+#ifndef __RUNNER_H__
+#define __RUNNER_H__
+
+#include
+
+#endif /* __RUNNER_H__ */
diff --git a/packages/tizen_app_control/example/tizen/ui/project_def.prop b/packages/tizen_app_control/example/tizen/ui/project_def.prop
new file mode 100644
index 000000000..e810dc649
--- /dev/null
+++ b/packages/tizen_app_control/example/tizen/ui/project_def.prop
@@ -0,0 +1,30 @@
+# See https://docs.tizen.org/application/tizen-studio/native-tools/project-conversion
+# for details.
+
+APPNAME = runner
+type = app
+profile = common-4.0
+
+# Source files
+USER_SRCS += src/runner.cc
+
+# User defines
+USER_DEFS =
+USER_UNDEFS =
+USER_CPP_DEFS = TIZEN_DEPRECATION DEPRECATION_WARNING
+USER_CPP_UNDEFS =
+
+# Compiler/linker flags
+USER_CFLAGS_MISC =
+USER_CPPFLAGS_MISC = -c -fmessage-length=0
+USER_LFLAGS = -Wl,-E
+
+# Libraries and objects
+USER_LIB_DIRS = lib
+USER_LIBS =
+USER_OBJS =
+
+# User includes
+USER_INC_DIRS = inc ../flutter src
+USER_INC_FILES =
+USER_CPP_INC_FILES =
diff --git a/packages/tizen_app_control/example/tizen/ui/shared/res/ic_launcher.png b/packages/tizen_app_control/example/tizen/ui/shared/res/ic_launcher.png
new file mode 100644
index 000000000..4d6372eeb
Binary files /dev/null and b/packages/tizen_app_control/example/tizen/ui/shared/res/ic_launcher.png differ
diff --git a/packages/tizen_app_control/example/tizen/ui/src/runner.cc b/packages/tizen_app_control/example/tizen/ui/src/runner.cc
new file mode 100644
index 000000000..74d25472a
--- /dev/null
+++ b/packages/tizen_app_control/example/tizen/ui/src/runner.cc
@@ -0,0 +1,18 @@
+#include "runner.h"
+
+#include "generated_plugin_registrant.h"
+
+class App : public FlutterApp {
+ public:
+ bool OnCreate() {
+ if (FlutterApp::OnCreate()) {
+ RegisterPlugins(this);
+ }
+ return IsRunning();
+ }
+};
+
+int main(int argc, char *argv[]) {
+ App app;
+ return app.Run(argc, argv);
+}
diff --git a/packages/tizen_app_control/example/tizen/ui/tizen-manifest.xml b/packages/tizen_app_control/example/tizen/ui/tizen-manifest.xml
new file mode 100644
index 000000000..5a6387f0c
--- /dev/null
+++ b/packages/tizen_app_control/example/tizen/ui/tizen-manifest.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+ ic_launcher.png
+
+
+ http://tizen.org/privilege/appmanager.launch
+ http://tizen.org/privilege/appmanager.kill.bgapp
+
+
+
diff --git a/packages/tizen_app_control/lib/app_control.dart b/packages/tizen_app_control/lib/app_control.dart
new file mode 100644
index 000000000..535a45538
--- /dev/null
+++ b/packages/tizen_app_control/lib/app_control.dart
@@ -0,0 +1,251 @@
+// Copyright 2021 Samsung Electronics Co., Ltd. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+library tizen_app_control;
+
+import 'dart:async';
+
+import 'package:flutter/services.dart';
+
+import 'app_manager.dart';
+import 'src/ffi.dart';
+import 'src/utils.dart';
+
+/// Enumeration for the application control launch mode.
+///
+/// For detailed information on Tizen's launch modes, see:
+/// https://docs.tizen.org/application/native/guides/app-management/app-controls/#application-group-management
+enum LaunchMode {
+ /// Prefer to launch the application as a main application in a new group.
+ single,
+
+ /// Prefer to launch the application as a sub application in the same group.
+ group,
+}
+
+/// Enumeration for the application control result.
+enum AppControlReplyResult {
+ /// Reserved for platform developers.
+ appStarted,
+
+ /// The callee application launched successfully.
+ succeeded,
+
+ /// The launch failed.
+ failed,
+
+ /// The operation has been canceled.
+ canceled,
+}
+
+/// Callback to be called when a reply to a launch request is delivered.
+///
+/// [request] represents the launch request that has been sent.
+/// [reply] represents the reply message sent by the callee application.
+/// [result] represents the result of launch.
+typedef AppControlReplyCallback = FutureOr Function(
+ AppControl request,
+ AppControl reply,
+ AppControlReplyResult result,
+);
+
+/// Represents a control message exchanged between applications.
+///
+/// An explicit or implicit control request can be made by an application to
+/// launch another application using this API. For detailed information on
+/// Tizen application controls, see:
+/// https://docs.tizen.org/application/native/guides/app-management/app-controls
+///
+/// For a list of common operation types and examples, see:
+/// https://docs.tizen.org/application/native/guides/app-management/common-appcontrols
+class AppControl {
+ /// Creates an instance of [AppControl] with the given parameters.
+ AppControl({
+ this.appId,
+ this.operation,
+ this.uri,
+ this.mime,
+ this.category,
+ this.launchMode = LaunchMode.single,
+ this.extraData = const {},
+ }) {
+ _id = nativeCreateAppControl(this);
+ if (_id < 0) {
+ throw Exception('Could not create an instance of AppControl.');
+ }
+ }
+
+ AppControl._fromMap(dynamic map)
+ : _id = map['id'] as int,
+ appId = map['appId'] as String?,
+ operation = map['operation'] as String?,
+ uri = map['uri'] as String?,
+ mime = map['mime'] as String?,
+ category = map['category'] as String?,
+ launchMode =
+ enumFromString(LaunchMode.values, map['launchMode'] as String),
+ extraData = Map.from(
+ map['extraData'] as Map) {
+ if (!nativeAttachAppControl(_id, this)) {
+ throw Exception('Could not find an instance of AppControl with ID $_id.');
+ }
+ }
+
+ /// The ID of the application to handle this request (applicable for explicit
+ /// requests).
+ ///
+ /// Either `appId` or `operation` must be set to non-null before sending a
+ /// request.
+ String? appId;
+
+ /// The operation to be performed by the callee application, such as
+ /// `http://tizen.org/appcontrol/operation/view`.
+ ///
+ /// If null, defaults to `http://tizen.org/appcontrol/operation/default`.
+ String? operation;
+
+ /// The URI of the data to be handled by this request.
+ ///
+ /// If the URI points to a file (`file://`) in the caller's data directory,
+ /// the callee process will be granted a read access to the file temporarily
+ /// during its lifetime.
+ String? uri;
+
+ /// The MIME type of the data to be handled by this request.
+ String? mime;
+
+ /// The type of the application that should handle this request, such as
+ /// `http://tizen.org/category/homeapp`.
+ String? category;
+
+ /// The launch mode, either [LaunchMode.single] or [LaunchMode.group].
+ ///
+ /// This value acts as a hint for the platform and cannot override the value
+ /// set in the callee's manifest file.
+ LaunchMode launchMode;
+
+ /// Additional information contained by this application control. Each value
+ /// must be either `String` or non-empty `List`.
+ Map extraData;
+
+ /// The unique ID internally used for managing application control handles.
+ late int _id;
+
+ static const MethodChannel _methodChannel =
+ MethodChannel('tizen/internal/app_control_method');
+
+ static const EventChannel _eventChannel =
+ EventChannel('tizen/internal/app_control_event');
+
+ /// A stream of incoming application controls.
+ static final Stream onAppControl = _eventChannel
+ .receiveBroadcastStream()
+ .map((dynamic event) => ReceivedAppControl._fromMap(
+ Map.from(event as Map)));
+
+ /// Sends a launch request to an application.
+ ///
+ /// The `http://tizen.org/privilege/appmanager.launch` privilege is required
+ /// to use this API.
+ ///
+ /// If [replyCallback] is null, this call returns immediately after sending
+ /// a request to the platform.
+ ///
+ /// If [replyCallback] is non-null, this call will not return until a reply
+ /// is received from the callee and [replyCallback] is invoked. If the callee
+ /// doesn't reply to the request or is terminated before replying, this call
+ /// will never return and [replyCallback] will never be invoked, resulting in
+ /// a memory leak.
+ Future sendLaunchRequest({
+ AppControlReplyCallback? replyCallback,
+ }) async {
+ await _setAppControlData();
+
+ final Map args = {
+ 'id': _id,
+ 'waitForReply': replyCallback != null,
+ };
+ if (replyCallback == null) {
+ await _methodChannel.invokeMethod('sendLaunchRequest', args);
+ } else {
+ final dynamic response =
+ await _methodChannel.invokeMethod('sendLaunchRequest', args);
+ final Map responseMap =
+ Map.from(response as Map);
+ final AppControlReplyResult result = enumFromString(
+ AppControlReplyResult.values,
+ responseMap['result'] as String,
+ AppControlReplyResult.failed,
+ );
+ final Map replyMap = Map.from(
+ responseMap['reply'] as Map);
+ final AppControl reply = AppControl._fromMap(replyMap);
+ await replyCallback(this, reply, result);
+ }
+ }
+
+ /// Sends a terminate request to a running application.
+ ///
+ /// This API can be only used to terminate sub applications launched by the
+ /// caller application as a group. To terminate background applications not
+ /// launched as a group, use [AppManager.terminateBackgroundApplication]
+ /// instead.
+ ///
+ /// Applications that were launched by the callee application as a group will
+ /// be terminated by this API as well.
+ Future sendTerminateRequest() async {
+ await _setAppControlData();
+
+ final Map args = {
+ 'id': _id,
+ };
+ await _methodChannel.invokeMethod('sendTerminateRequest', args);
+ }
+
+ Future _setAppControlData() async {
+ final Map args = {
+ 'id': _id,
+ 'appId': appId,
+ 'operation': operation,
+ 'uri': uri,
+ 'mime': mime,
+ 'category': category,
+ 'launchMode': enumToString(launchMode),
+ 'extraData': extraData,
+ };
+ await _methodChannel.invokeMethod('setAppControlData', args);
+ }
+}
+
+/// Represents a received [AppControl] message.
+class ReceivedAppControl extends AppControl {
+ ReceivedAppControl._fromMap(dynamic map)
+ : callerAppId = map['callerAppId'] as String?,
+ shouldReply = map['shouldReply'] as bool,
+ super._fromMap(map);
+
+ /// The caller application ID.
+ final String? callerAppId;
+
+ /// Whether a reply is requested.
+ ///
+ /// This is true when the caller application provided non-null
+ /// `replyCallback` for [AppControl.sendLaunchRequest].
+ final bool shouldReply;
+
+ /// Replies to a launch request.
+ ///
+ /// [reply] and [result] are sent back to the caller application and set as
+ /// arguments of [AppControlReplyCallback].
+ Future reply(AppControl reply, AppControlReplyResult result) async {
+ await reply._setAppControlData();
+
+ final Map args = {
+ 'id': _id,
+ 'replyId': reply._id,
+ 'result': enumToString(result),
+ };
+ await AppControl._methodChannel.invokeMethod('reply', args);
+ }
+}
diff --git a/packages/tizen_app_control/lib/app_manager.dart b/packages/tizen_app_control/lib/app_manager.dart
new file mode 100644
index 000000000..3ae180b9d
--- /dev/null
+++ b/packages/tizen_app_control/lib/app_manager.dart
@@ -0,0 +1,46 @@
+// Copyright 2021 Samsung Electronics Co., Ltd. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// ignore_for_file: always_specify_types
+
+library tizen_app_control;
+
+import 'dart:ffi';
+
+import 'package:ffi/ffi.dart';
+
+import 'src/ffi.dart';
+import 'src/utils.dart';
+
+/// Provides information about installed and running applications.
+class AppManager {
+ const AppManager._();
+
+ /// Returns true if an application with the given [appId] is running,
+ /// otherwise false.
+ static bool isRunning(String appId) {
+ return using((Arena arena) {
+ final Pointer running = arena();
+ throwOnError(
+ appManagerIsRunning(appId.toNativeUtf8(allocator: arena), running));
+ return running.value > 0;
+ });
+ }
+
+ /// Sends a terminate request to an application with the given [appId].
+ /// UI applications that are in a paused state and service applications can
+ /// be terminated by this API.
+ ///
+ /// The `http://tizen.org/privilege/appmanager.kill.bgapp` privilege is
+ /// required to use this API.
+ static void terminateBackgroundApplication(String appId) {
+ using((Arena arena) {
+ final Pointer appContext = arena();
+ throwOnError(appManagerGetAppContext(
+ appId.toNativeUtf8(allocator: arena), appContext));
+ throwOnError(appManagerRequestTerminateBgApp(appContext.value));
+ throwOnError(appContextDestroy(appContext.value));
+ });
+ }
+}
diff --git a/packages/tizen_app_control/lib/src/ffi.dart b/packages/tizen_app_control/lib/src/ffi.dart
new file mode 100644
index 000000000..fad83a8bb
--- /dev/null
+++ b/packages/tizen_app_control/lib/src/ffi.dart
@@ -0,0 +1,105 @@
+// Copyright 2021 Samsung Electronics Co., Ltd. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// ignore_for_file: public_member_api_docs
+// ignore_for_file: always_specify_types
+
+import 'dart:ffi';
+
+import 'package:ffi/ffi.dart';
+
+typedef _InitializeDartApiNative = IntPtr Function(Pointer);
+typedef _InitializeDartApi = int Function(Pointer);
+typedef _CreateAppControlNative = Uint32 Function(Handle);
+typedef _CreateAppControl = int Function(Object);
+typedef _AttachAppControlNative = Int8 Function(Int32, Handle);
+typedef _AttachAppControl = int Function(int, Object);
+
+DynamicLibrary? _libEmbedderCache;
+
+DynamicLibrary get _libEmbedder {
+ if (_libEmbedderCache == null) {
+ const List embedderPaths = [
+ 'libflutter_tizen.so',
+ 'libflutter_tizen_common.so',
+ 'libflutter_tizen_mobile.so',
+ 'libflutter_tizen_tv.so',
+ 'libflutter_tizen_wearable.so',
+ ];
+ for (final String path in embedderPaths) {
+ try {
+ _libEmbedderCache = DynamicLibrary.open(path);
+ break;
+ } on ArgumentError {
+ continue;
+ }
+ }
+ if (_libEmbedderCache == null) {
+ throw Exception('Failed to load the embedder library.');
+ }
+ final _InitializeDartApi initFunction = _libEmbedder.lookupFunction<
+ _InitializeDartApiNative,
+ _InitializeDartApi>('NativeInitializeDartApi');
+ initFunction(NativeApi.initializeApiDLData);
+ }
+ return _libEmbedderCache!;
+}
+
+final _CreateAppControl nativeCreateAppControl =
+ _libEmbedder.lookupFunction<_CreateAppControlNative, _CreateAppControl>(
+ 'NativeCreateAppControl');
+final _AttachAppControl _nativeAttachAppControl =
+ _libEmbedder.lookupFunction<_AttachAppControlNative, _AttachAppControl>(
+ 'NativeAttachAppControl');
+
+bool nativeAttachAppControl(int id, Object dartObject) {
+ return _nativeAttachAppControl(id, dartObject) > 0;
+}
+
+class _AppContext extends Opaque {}
+
+typedef AppContextHandle = Pointer<_AppContext>;
+
+typedef _AppContextDestroyNative = Int32 Function(AppContextHandle);
+typedef _AppContextDestroy = int Function(AppContextHandle);
+
+typedef _AppManagerGetAppContextNative = Int32 Function(
+ Pointer, Pointer);
+typedef _AppManagerGetAppContext = int Function(
+ Pointer, Pointer);
+typedef _AppManagerIsRunningNative = Int32 Function(
+ Pointer, Pointer);
+typedef _AppManagerIsRunning = int Function(Pointer, Pointer);
+typedef _AppManagerRequestTerminateBgAppNative = Int32 Function(
+ AppContextHandle);
+typedef _AppManagerRequestTerminateBgApp = int Function(AppContextHandle);
+
+final DynamicLibrary _libAppManager =
+ DynamicLibrary.open('libcapi-appfw-app-manager.so.0');
+
+final _AppManagerGetAppContext appManagerGetAppContext = _libAppManager
+ .lookupFunction<_AppManagerGetAppContextNative, _AppManagerGetAppContext>(
+ 'app_manager_get_app_context');
+
+final _AppContextDestroy appContextDestroy =
+ _libAppManager.lookupFunction<_AppContextDestroyNative, _AppContextDestroy>(
+ 'app_context_destroy');
+
+final _AppManagerRequestTerminateBgApp appManagerRequestTerminateBgApp =
+ _libAppManager.lookupFunction<_AppManagerRequestTerminateBgAppNative,
+ _AppManagerRequestTerminateBgApp>(
+ 'app_manager_request_terminate_bg_app');
+
+final _AppManagerIsRunning appManagerIsRunning = _libAppManager.lookupFunction<
+ _AppManagerIsRunningNative, _AppManagerIsRunning>('app_manager_is_running');
+
+typedef _GetErrorMessageNative = Pointer Function(Int32);
+typedef _GetErrorMessage = Pointer Function(int);
+
+final DynamicLibrary _libBaseCommon =
+ DynamicLibrary.open('libcapi-base-common.so.0');
+
+final _GetErrorMessage getErrorMessage =
+ _libBaseCommon.lookupFunction<_GetErrorMessageNative, _GetErrorMessage>(
+ 'get_error_message');
diff --git a/packages/tizen_app_control/lib/src/utils.dart b/packages/tizen_app_control/lib/src/utils.dart
new file mode 100644
index 000000000..6c8f922f1
--- /dev/null
+++ b/packages/tizen_app_control/lib/src/utils.dart
@@ -0,0 +1,34 @@
+// Copyright 2021 Samsung Electronics Co., Ltd. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:ffi/ffi.dart';
+import 'package:flutter/services.dart';
+
+import 'ffi.dart';
+
+String enumToString(E enumValue) {
+ return enumValue.toString().split('.').last;
+}
+
+E enumFromString(
+ List enumValues,
+ String stringValue, [
+ E? defaultValue,
+]) {
+ return enumValues.firstWhere(
+ (E e) => e.toString().split('.').last == stringValue,
+ orElse: () => defaultValue ?? enumValues.first,
+ );
+}
+
+void throwOnError(int ret) {
+ if (ret != 0) {
+ throw PlatformException(
+ code: ret.toString(),
+ message: getErrorMessage(ret).toDartString(),
+ );
+ }
+}
diff --git a/packages/tizen_app_control/pubspec.yaml b/packages/tizen_app_control/pubspec.yaml
new file mode 100644
index 000000000..7297d53f8
--- /dev/null
+++ b/packages/tizen_app_control/pubspec.yaml
@@ -0,0 +1,14 @@
+name: tizen_app_control
+description: Tizen application control APIs. Used for launching and terminating
+ applications on a Tizen device.
+homepage: https://github.com/flutter-tizen/plugins
+version: 0.1.0
+
+dependencies:
+ ffi: ^1.1.2
+ flutter:
+ sdk: flutter
+
+environment:
+ sdk: ">=2.13.0 <3.0.0"
+ flutter: ">=2.2.0"