From 090947c96491045bbb79ac6164084d7078db0c9c Mon Sep 17 00:00:00 2001 From: Swift Kim Date: Fri, 10 Sep 2021 23:53:59 +0900 Subject: [PATCH 1/9] [tizen_app_control] Add tizen_app_control package --- packages/tizen_app_control/.gitignore | 75 ++++++ packages/tizen_app_control/CHANGELOG.md | 3 + packages/tizen_app_control/LICENSE | 25 ++ packages/tizen_app_control/README.md | 3 + packages/tizen_app_control/example/.gitignore | 46 ++++ packages/tizen_app_control/example/README.md | 3 + .../tizen_app_control/example/lib/main.dart | 147 ++++++++++++ .../tizen_app_control/example/pubspec.yaml | 13 + .../example/tizen/.gitignore | 1 + .../example/tizen/service/.exportMap | 5 + .../example/tizen/service/.gitignore | 8 + .../example/tizen/service/inc/runner.h | 6 + .../example/tizen/service/project_def.prop | 30 +++ .../tizen/service/shared/res/ic_launcher.png | Bin 0 -> 1443 bytes .../example/tizen/service/src/runner.cc | 19 ++ .../example/tizen/service/tizen-manifest.xml | 10 + .../example/tizen/ui/.exportMap | 5 + .../example/tizen/ui/.gitignore | 8 + .../example/tizen/ui/inc/runner.h | 6 + .../example/tizen/ui/project_def.prop | 30 +++ .../tizen/ui/shared/res/ic_launcher.png | Bin 0 -> 1443 bytes .../example/tizen/ui/src/runner.cc | 18 ++ .../example/tizen/ui/tizen-manifest.xml | 13 + .../tizen_app_control/lib/app_control.dart | 223 ++++++++++++++++++ .../tizen_app_control/lib/app_manager.dart | 46 ++++ packages/tizen_app_control/lib/src/ffi.dart | 95 ++++++++ packages/tizen_app_control/lib/src/utils.dart | 34 +++ packages/tizen_app_control/pubspec.yaml | 14 ++ 28 files changed, 886 insertions(+) create mode 100644 packages/tizen_app_control/.gitignore create mode 100644 packages/tizen_app_control/CHANGELOG.md create mode 100644 packages/tizen_app_control/LICENSE create mode 100644 packages/tizen_app_control/README.md create mode 100644 packages/tizen_app_control/example/.gitignore create mode 100644 packages/tizen_app_control/example/README.md create mode 100644 packages/tizen_app_control/example/lib/main.dart create mode 100644 packages/tizen_app_control/example/pubspec.yaml create mode 100644 packages/tizen_app_control/example/tizen/.gitignore create mode 100644 packages/tizen_app_control/example/tizen/service/.exportMap create mode 100644 packages/tizen_app_control/example/tizen/service/.gitignore create mode 100644 packages/tizen_app_control/example/tizen/service/inc/runner.h create mode 100644 packages/tizen_app_control/example/tizen/service/project_def.prop create mode 100644 packages/tizen_app_control/example/tizen/service/shared/res/ic_launcher.png create mode 100644 packages/tizen_app_control/example/tizen/service/src/runner.cc create mode 100644 packages/tizen_app_control/example/tizen/service/tizen-manifest.xml create mode 100644 packages/tizen_app_control/example/tizen/ui/.exportMap create mode 100644 packages/tizen_app_control/example/tizen/ui/.gitignore create mode 100644 packages/tizen_app_control/example/tizen/ui/inc/runner.h create mode 100644 packages/tizen_app_control/example/tizen/ui/project_def.prop create mode 100644 packages/tizen_app_control/example/tizen/ui/shared/res/ic_launcher.png create mode 100644 packages/tizen_app_control/example/tizen/ui/src/runner.cc create mode 100644 packages/tizen_app_control/example/tizen/ui/tizen-manifest.xml create mode 100644 packages/tizen_app_control/lib/app_control.dart create mode 100644 packages/tizen_app_control/lib/app_manager.dart create mode 100644 packages/tizen_app_control/lib/src/ffi.dart create mode 100644 packages/tizen_app_control/lib/src/utils.dart create mode 100644 packages/tizen_app_control/pubspec.yaml 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..ac9389db8 --- /dev/null +++ b/packages/tizen_app_control/README.md @@ -0,0 +1,3 @@ +# tizen_app_control + +A new Flutter package project. 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..90f18f4fc --- /dev/null +++ b/packages/tizen_app_control/example/README.md @@ -0,0 +1,3 @@ +# tizen_app_control_example + +A new Flutter project. 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..07d5fa720 --- /dev/null +++ b/packages/tizen_app_control/example/lib/main.dart @@ -0,0 +1,147 @@ +// 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 { + 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 _launchService() async { + // Send a launch request to the service app. + final AppControl request = AppControl(appId: _kServiceAppId); + return request.sendLaunchRequest( + replyCallback: ( + AppControl request, + AppControl reply, + AppControlReplyResult result, + ) async { + if (result == AppControlReplyResult.succeeded) { + setState(() { + _isServiceStarted = true; + }); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Launch $result.')), + ); + } + }, + ); + } + + Future _terminateService() async { + // Send a terminate request to the service app. + AppManager.terminateBackgroundApplication(_kServiceAppId); + setState(() { + _isServiceStarted = AppManager.isRunning(_kServiceAppId); + }); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar(title: const Text('Tizen App Control Example')), + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ElevatedButton( + onPressed: _isServiceStarted ? null : _launchService, + child: const Text('Launch service'), + ), + const SizedBox(height: 10), + ElevatedButton( + onPressed: _isServiceStarted ? _terminateService : null, + child: const Text('Terminate 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..d448f99b8 --- /dev/null +++ b/packages/tizen_app_control/example/pubspec.yaml @@ -0,0 +1,13 @@ +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: ../ + +environment: + sdk: ">=2.13.0 <3.0.0" 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 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 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..bc63e37d3 --- /dev/null +++ b/packages/tizen_app_control/lib/app_control.dart @@ -0,0 +1,223 @@ +// 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 = Function( + AppControl request, + AppControl reply, + AppControlReplyResult result, +); + +/// Represents a control message exchanged between applications. +/// +/// An explicit or implicit control request may be sent by an application to +/// launch another application. For detailed information, 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); + } + + 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); + + /// The ID of the application to handle this request (applicable for explicit + /// requests). + String appId; + + /// The operation to be performed by the target application, such as + /// `http://tizen.org/appcontrol/operation/view`. + String operation; + + /// The URI of the data to be handled by this request. + String uri; + + /// The MIME type of the data to be handled by this request. + String mime; + + /// The category of application that should handle this request, such as + /// `http://tizen.org/category/homeapp`. + String category; + + /// The launch mode, either [LaunchMode.single] or [LaunchMode.group]. + LaunchMode launchMode; + + /// Additional information required to perform the operation. + 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 Stream get 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. + /// + /// If [replyCallback] is non-null, this call will not return until a reply + /// is received from the callee and [replyCallback] is invoked. + Future sendLaunchRequest({ + AppControlReplyCallback? replyCallback, + }) async { + await _setAppControlData(); + + final Map args = { + 'id': _id, + 'waitForReply': replyCallback != null, + }; + if (replyCallback == null) { + return _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); + 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. + Future sendTerminateRequest() async { + await _setAppControlData(); + + final Map args = { + 'id': _id, + }; + return _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, + }; + return _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': reply._id, + 'requestId': _id, + 'result': enumToString(result), + }; + return 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..4d9400a5b --- /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 using 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..ec797893f --- /dev/null +++ b/packages/tizen_app_control/lib/src/ffi.dart @@ -0,0 +1,95 @@ +// 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); + +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 { + // No-op. + } + } + 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'); + +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" From a6096d70b5c4b2c7fa7fc5cd3bec17c8ba305a3e Mon Sep 17 00:00:00 2001 From: Swift Kim Date: Sat, 11 Sep 2021 19:41:28 +0900 Subject: [PATCH 2/9] Update README --- .github/labeler.yml | 2 + README.md | 4 +- packages/tizen_app_control/README.md | 68 +++++++++++++++++++- packages/tizen_app_control/example/README.md | 6 +- 4 files changed, 77 insertions(+), 3 deletions(-) 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/README.md b/README.md index e76002da3..ee8e43b20 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # plugins -[![Build](https://github.com/flutter-tizen/plugins/actions/workflows/build.yml/badge.svg)](https://github.com/flutter-tizen/plugins/actions) +[![Build](https://github.com/flutter-tizen/plugins/actions/workflows/build.yml/badge.svg)](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) | [![pub package](https://img.shields.io/pub/v/sensors_plus_tizen.svg)](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) | [![pub package](https://img.shields.io/pub/v/share_plus_tizen.svg)](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) | [![pub package](https://img.shields.io/pub/v/shared_preferences_tizen.svg)](https://pub.dev/packages/shared_preferences_tizen) | No | +| [**tizen_app_control**](packages/tizen_app_control) | (Tizen-only) | [![pub package](https://img.shields.io/pub/v/tizen_app_control.svg)](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) | [![pub package](https://img.shields.io/pub/v/url_launcher_tizen.svg)](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) | [![pub package](https://img.shields.io/pub/v/video_player_tizen.svg)](https://pub.dev/packages/video_player_tizen) | No | | [**wakelock_tizen**](packages/wakelock) | [wakelock](https://github.com/creativecreatorormaybenot/wakelock) (3rd-party) | [![pub package](https://img.shields.io/pub/v/wakelock_tizen.svg)](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/README.md b/packages/tizen_app_control/README.md index ac9389db8..a335df0f2 100644 --- a/packages/tizen_app_control/README.md +++ b/packages/tizen_app_control/README.md @@ -1,3 +1,69 @@ # tizen_app_control -A new Flutter package project. +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/README.md b/packages/tizen_app_control/example/README.md index 90f18f4fc..755f6a43d 100644 --- a/packages/tizen_app_control/example/README.md +++ b/packages/tizen_app_control/example/README.md @@ -1,3 +1,7 @@ # tizen_app_control_example -A new Flutter project. +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). From 86be33f8b3f8b4c591d7cdece399de32da0d1ab9 Mon Sep 17 00:00:00 2001 From: Swift Kim Date: Sat, 11 Sep 2021 20:14:45 +0900 Subject: [PATCH 3/9] Clean up --- .../tizen_app_control/example/lib/main.dart | 73 ++++++++++++++++--- .../tizen_app_control/lib/app_control.dart | 56 +++++++------- .../tizen_app_control/lib/app_manager.dart | 2 +- packages/tizen_app_control/lib/src/ffi.dart | 2 +- 4 files changed, 93 insertions(+), 40 deletions(-) diff --git a/packages/tizen_app_control/example/lib/main.dart b/packages/tizen_app_control/example/lib/main.dart index 07d5fa720..fee1f6b5f 100644 --- a/packages/tizen_app_control/example/lib/main.dart +++ b/packages/tizen_app_control/example/lib/main.dart @@ -26,7 +26,7 @@ void serviceMain() { WidgetsFlutterBinding.ensureInitialized(); // Listen for incoming AppControls. - final StreamSubscription appControlListener = + final StreamSubscription appControlListener = AppControl.onAppControl.listen((ReceivedAppControl request) async { if (request.shouldReply) { final AppControl reply = AppControl(); @@ -62,6 +62,8 @@ class MyApp extends StatefulWidget { } class _MyAppState extends State { + final GlobalKey _messengerKey = + GlobalKey(); LocalPort? _localPort; int _messagesCount = 0; bool _isServiceStarted = false; @@ -88,22 +90,59 @@ class _MyAppState extends State { _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 { - // Send a launch request to the service app. final AppControl request = AppControl(appId: _kServiceAppId); - return request.sendLaunchRequest( + await request.sendLaunchRequest( replyCallback: ( AppControl request, AppControl reply, AppControlReplyResult result, - ) async { + ) { if (result == AppControlReplyResult.succeeded) { setState(() { _isServiceStarted = true; }); } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Launch $result.')), + _messengerKey.currentState!.showSnackBar( + const SnackBar(content: Text('Launch failed.')), ); } }, @@ -111,7 +150,6 @@ class _MyAppState extends State { } Future _terminateService() async { - // Send a terminate request to the service app. AppManager.terminateBackgroundApplication(_kServiceAppId); setState(() { _isServiceStarted = AppManager.isRunning(_kServiceAppId); @@ -121,6 +159,7 @@ class _MyAppState extends State { @override Widget build(BuildContext context) { return MaterialApp( + scaffoldMessengerKey: _messengerKey, home: Scaffold( appBar: AppBar(title: const Text('Tizen App Control Example')), body: Center( @@ -128,15 +167,27 @@ class _MyAppState extends State { mainAxisSize: MainAxisSize.min, children: [ ElevatedButton( - onPressed: _isServiceStarted ? null : _launchService, - child: const Text('Launch service'), + onPressed: _sendSms, + child: const Text('Send SMS'), ), const SizedBox(height: 10), ElevatedButton( - onPressed: _isServiceStarted ? _terminateService : null, - child: const Text('Terminate service'), + 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/lib/app_control.dart b/packages/tizen_app_control/lib/app_control.dart index bc63e37d3..c39d12f87 100644 --- a/packages/tizen_app_control/lib/app_control.dart +++ b/packages/tizen_app_control/lib/app_control.dart @@ -44,7 +44,7 @@ enum AppControlReplyResult { /// [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 = Function( +typedef AppControlReplyCallback = FutureOr Function( AppControl request, AppControl reply, AppControlReplyResult result, @@ -61,11 +61,11 @@ typedef AppControlReplyCallback = Function( class AppControl { /// Creates an instance of [AppControl] with the given parameters. AppControl({ - this.appId = '', - this.operation = '', - this.uri = '', - this.mime = '', - this.category = '', + this.appId, + this.operation, + this.uri, + this.mime, + this.category, this.launchMode = LaunchMode.single, this.extraData = const {}, }) { @@ -74,11 +74,11 @@ class 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, + 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( @@ -86,26 +86,27 @@ class AppControl { /// The ID of the application to handle this request (applicable for explicit /// requests). - String appId; + String? appId; - /// The operation to be performed by the target application, such as + /// The operation to be performed by the callee, such as /// `http://tizen.org/appcontrol/operation/view`. - String operation; + String? operation; /// The URI of the data to be handled by this request. - String uri; + String? uri; /// The MIME type of the data to be handled by this request. - String mime; + String? mime; - /// The category of application that should handle this request, such as + /// The type of the application that should handle this request, such as /// `http://tizen.org/category/homeapp`. - String category; + String? category; /// The launch mode, either [LaunchMode.single] or [LaunchMode.group]. LaunchMode launchMode; - /// Additional information required to perform the operation. + /// Additional information contained by this application control. Each value + /// must be either `String` or `List`. Map extraData; /// The unique ID internally used for managing application control handles. @@ -128,7 +129,8 @@ class AppControl { /// The `http://tizen.org/privilege/appmanager.launch` privilege is required /// to use this API. /// - /// If [replyCallback] is null, this call returns immediately. + /// 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. @@ -142,7 +144,7 @@ class AppControl { 'waitForReply': replyCallback != null, }; if (replyCallback == null) { - return _methodChannel.invokeMethod('sendLaunchRequest', args); + await _methodChannel.invokeMethod('sendLaunchRequest', args); } else { final dynamic response = await _methodChannel.invokeMethod('sendLaunchRequest', args); @@ -156,7 +158,7 @@ class AppControl { final Map replyMap = Map.from( responseMap['reply'] as Map); final AppControl reply = AppControl._fromMap(replyMap); - replyCallback(this, reply, result); + await replyCallback(this, reply, result); } } @@ -172,7 +174,7 @@ class AppControl { final Map args = { 'id': _id, }; - return _methodChannel.invokeMethod('sendTerminateRequest', args); + await _methodChannel.invokeMethod('sendTerminateRequest', args); } Future _setAppControlData() async { @@ -186,19 +188,19 @@ class AppControl { 'launchMode': enumToString(launchMode), 'extraData': extraData, }; - return _methodChannel.invokeMethod('setAppControlData', args); + await _methodChannel.invokeMethod('setAppControlData', args); } } /// Represents a received [AppControl] message. class ReceivedAppControl extends AppControl { ReceivedAppControl._fromMap(dynamic map) - : callerAppId = map['callerAppId'] as String, + : callerAppId = map['callerAppId'] as String?, shouldReply = map['shouldReply'] as bool, super._fromMap(map); /// The caller application ID. - final String callerAppId; + final String? callerAppId; /// Whether a reply is requested. /// @@ -218,6 +220,6 @@ class ReceivedAppControl extends AppControl { 'requestId': _id, 'result': enumToString(result), }; - return AppControl._methodChannel.invokeMethod('reply', args); + 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 index 4d9400a5b..3ae180b9d 100644 --- a/packages/tizen_app_control/lib/app_manager.dart +++ b/packages/tizen_app_control/lib/app_manager.dart @@ -30,7 +30,7 @@ class AppManager { /// 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 using this API. + /// be terminated by this API. /// /// The `http://tizen.org/privilege/appmanager.kill.bgapp` privilege is /// required to use this API. diff --git a/packages/tizen_app_control/lib/src/ffi.dart b/packages/tizen_app_control/lib/src/ffi.dart index ec797893f..b14834775 100644 --- a/packages/tizen_app_control/lib/src/ffi.dart +++ b/packages/tizen_app_control/lib/src/ffi.dart @@ -30,7 +30,7 @@ DynamicLibrary get _libEmbedder { _libEmbedderCache = DynamicLibrary.open(path); break; } on ArgumentError { - // No-op. + continue; } } if (_libEmbedderCache == null) { From 3afa28b3aba8f3da2499f13d02e82d97546b9374 Mon Sep 17 00:00:00 2001 From: Swift Kim Date: Sun, 12 Sep 2021 18:31:39 +0900 Subject: [PATCH 4/9] Support nativeAttachAppControl --- packages/tizen_app_control/lib/app_control.dart | 14 +++++++++++--- packages/tizen_app_control/lib/app_manager.dart | 2 -- packages/tizen_app_control/lib/src/ffi.dart | 10 +++++++++- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/packages/tizen_app_control/lib/app_control.dart b/packages/tizen_app_control/lib/app_control.dart index c39d12f87..244c560d5 100644 --- a/packages/tizen_app_control/lib/app_control.dart +++ b/packages/tizen_app_control/lib/app_control.dart @@ -52,8 +52,9 @@ typedef AppControlReplyCallback = FutureOr Function( /// Represents a control message exchanged between applications. /// -/// An explicit or implicit control request may be sent by an application to -/// launch another application. For detailed information, see: +/// 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: @@ -70,6 +71,9 @@ class AppControl { this.extraData = const {}, }) { _id = nativeCreateAppControl(this); + if (_id < 0) { + throw Exception('Could not create an instance of AppControl.'); + } } AppControl._fromMap(dynamic map) @@ -82,7 +86,11 @@ class AppControl { launchMode = enumFromString(LaunchMode.values, map['launchMode'] as String), extraData = Map.from( - map['extraData'] as Map); + 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). diff --git a/packages/tizen_app_control/lib/app_manager.dart b/packages/tizen_app_control/lib/app_manager.dart index 3ae180b9d..7d99c69c7 100644 --- a/packages/tizen_app_control/lib/app_manager.dart +++ b/packages/tizen_app_control/lib/app_manager.dart @@ -2,8 +2,6 @@ // 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'; diff --git a/packages/tizen_app_control/lib/src/ffi.dart b/packages/tizen_app_control/lib/src/ffi.dart index b14834775..d6bbb5ea4 100644 --- a/packages/tizen_app_control/lib/src/ffi.dart +++ b/packages/tizen_app_control/lib/src/ffi.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. // ignore_for_file: public_member_api_docs -// ignore_for_file: always_specify_types import 'dart:ffi'; @@ -13,6 +12,8 @@ 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; @@ -47,6 +48,13 @@ DynamicLibrary get _libEmbedder { 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 {} From d29a82095c80e8309ea716de9f7458db27fcaf52 Mon Sep 17 00:00:00 2001 From: Swift Kim Date: Mon, 13 Sep 2021 15:39:34 +0900 Subject: [PATCH 5/9] Add integration_test --- .../tizen_app_control_test.dart | 126 ++++++++++++++++++ .../tizen_app_control/example/pubspec.yaml | 10 ++ .../example/test_driver/integration_test.dart | 3 + .../tizen_app_control/lib/app_control.dart | 2 +- 4 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 packages/tizen_app_control/example/integration_test/tizen_app_control_test.dart create mode 100644 packages/tizen_app_control/example/test_driver/integration_test.dart 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..f38b4755f --- /dev/null +++ b/packages/tizen_app_control/example/integration_test/tizen_app_control_test.dart @@ -0,0 +1,126 @@ +// 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.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/pubspec.yaml b/packages/tizen_app_control/example/pubspec.yaml index d448f99b8..338154a2d 100644 --- a/packages/tizen_app_control/example/pubspec.yaml +++ b/packages/tizen_app_control/example/pubspec.yaml @@ -9,5 +9,15 @@ dependencies: 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/lib/app_control.dart b/packages/tizen_app_control/lib/app_control.dart index 244c560d5..851933fe7 100644 --- a/packages/tizen_app_control/lib/app_control.dart +++ b/packages/tizen_app_control/lib/app_control.dart @@ -127,7 +127,7 @@ class AppControl { EventChannel('tizen/internal/app_control_event'); /// A stream of incoming application controls. - static Stream get onAppControl => _eventChannel + static final Stream onAppControl = _eventChannel .receiveBroadcastStream() .map((dynamic event) => ReceivedAppControl._fromMap( Map.from(event as Map))); From e3387a937dd7a3a039c5fa130d214558335435cc Mon Sep 17 00:00:00 2001 From: Swift Kim Date: Mon, 13 Sep 2021 17:47:30 +0900 Subject: [PATCH 6/9] Create a certificate profile in the CI builder --- .github/workflows/build.yml | 6 ++++++ 1 file changed, 6 insertions(+) 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: | From 8b5ab599f7fec34a843a4f7fb69d70c33781b0cd Mon Sep 17 00:00:00 2001 From: Swift Kim Date: Tue, 14 Sep 2021 16:35:20 +0900 Subject: [PATCH 7/9] Add more documentation --- .../tizen_app_control/lib/app_control.dart | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/tizen_app_control/lib/app_control.dart b/packages/tizen_app_control/lib/app_control.dart index 851933fe7..85d3fca8d 100644 --- a/packages/tizen_app_control/lib/app_control.dart +++ b/packages/tizen_app_control/lib/app_control.dart @@ -94,13 +94,22 @@ class AppControl { /// 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, such as + /// 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. @@ -111,6 +120,9 @@ class AppControl { 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 @@ -141,7 +153,10 @@ class AppControl { /// 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. + /// 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 { @@ -176,6 +191,9 @@ class AppControl { /// 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(); From d29131bf22d47cf89231d51d8c2d8a14f71f567e Mon Sep 17 00:00:00 2001 From: Swift Kim Date: Thu, 16 Sep 2021 14:20:43 +0900 Subject: [PATCH 8/9] Use replyId instead of requestId --- .../example/integration_test/tizen_app_control_test.dart | 5 +++++ packages/tizen_app_control/lib/app_control.dart | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) 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 index f38b4755f..23592aee3 100644 --- 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 @@ -37,6 +37,11 @@ void main() { 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); diff --git a/packages/tizen_app_control/lib/app_control.dart b/packages/tizen_app_control/lib/app_control.dart index 85d3fca8d..535a45538 100644 --- a/packages/tizen_app_control/lib/app_control.dart +++ b/packages/tizen_app_control/lib/app_control.dart @@ -126,7 +126,7 @@ class AppControl { LaunchMode launchMode; /// Additional information contained by this application control. Each value - /// must be either `String` or `List`. + /// must be either `String` or non-empty `List`. Map extraData; /// The unique ID internally used for managing application control handles. @@ -242,8 +242,8 @@ class ReceivedAppControl extends AppControl { await reply._setAppControlData(); final Map args = { - 'id': reply._id, - 'requestId': _id, + 'id': _id, + 'replyId': reply._id, 'result': enumToString(result), }; await AppControl._methodChannel.invokeMethod('reply', args); From 5b438c5d168430b418f9b2b1a0cecec15e71b44b Mon Sep 17 00:00:00 2001 From: Swift Kim Date: Fri, 17 Sep 2021 11:35:29 +0900 Subject: [PATCH 9/9] Fix linter errors --- packages/tizen_app_control/lib/app_manager.dart | 2 ++ packages/tizen_app_control/lib/src/ffi.dart | 2 ++ 2 files changed, 4 insertions(+) diff --git a/packages/tizen_app_control/lib/app_manager.dart b/packages/tizen_app_control/lib/app_manager.dart index 7d99c69c7..3ae180b9d 100644 --- a/packages/tizen_app_control/lib/app_manager.dart +++ b/packages/tizen_app_control/lib/app_manager.dart @@ -2,6 +2,8 @@ // 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'; diff --git a/packages/tizen_app_control/lib/src/ffi.dart b/packages/tizen_app_control/lib/src/ffi.dart index d6bbb5ea4..fad83a8bb 100644 --- a/packages/tizen_app_control/lib/src/ffi.dart +++ b/packages/tizen_app_control/lib/src/ffi.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. // ignore_for_file: public_member_api_docs +// ignore_for_file: always_specify_types import 'dart:ffi'; @@ -59,6 +60,7 @@ bool nativeAttachAppControl(int id, Object dartObject) { class _AppContext extends Opaque {} typedef AppContextHandle = Pointer<_AppContext>; + typedef _AppContextDestroyNative = Int32 Function(AppContextHandle); typedef _AppContextDestroy = int Function(AppContextHandle);