From a9d357f423241b380b570c9a76c20ce01b72f7b0 Mon Sep 17 00:00:00 2001 From: Jeongkyun Date: Mon, 28 Feb 2022 16:27:59 +0900 Subject: [PATCH] [tizen_package_manager] Add tizen_package_manager package (#330) * initial commit of package manager * fix README file * fix code review issues * remove install/uninstall test and event listen test * fix code review issues. * fix dart analyze issue * remove space in README * fix code review issues on native implementation * additional fix on native implementation * Remove plugin definition file and callback function move into class static member --- .github/labeler.yml | 2 + .github/recipe.yaml | 1 + README.md | 2 + packages/tizen_package_manager/.gitignore | 7 + packages/tizen_package_manager/CHANGELOG.md | 4 + packages/tizen_package_manager/LICENSE | 25 ++ packages/tizen_package_manager/README.md | 58 +++ .../tizen_package_manager/example/.gitignore | 46 ++ .../tizen_package_manager/example/README.md | 7 + .../tizen_package_manager_test.dart | 32 ++ .../example/lib/main.dart | 188 ++++++++ .../example/pubspec.yaml | 26 ++ .../example/test_driver/integration_test.dart | 3 + .../example/tizen/.gitignore | 5 + .../example/tizen/App.cs | 20 + .../example/tizen/Runner.csproj | 26 ++ .../example/tizen/shared/res/ic_launcher.png | Bin 0 -> 1443 bytes .../shared/res/org.example.simplehome.tpk | Bin 0 -> 39721 bytes .../example/tizen/tizen-manifest.xml | 13 + .../lib/package_manager.dart | 304 +++++++++++++ packages/tizen_package_manager/pubspec.yaml | 21 + .../tizen_package_manager/tizen/.gitignore | 5 + .../tizen/inc/tizen_package_manager_plugin.h | 27 ++ .../tizen/project_def.prop | 24 + .../tizen_package_manager/tizen/src/log.h | 24 + .../tizen/src/package_manager_utils.cc | 173 +++++++ .../tizen/src/package_manager_utils.h | 30 ++ .../tizen/src/tizen_package_manager_plugin.cc | 421 ++++++++++++++++++ 28 files changed, 1494 insertions(+) create mode 100644 packages/tizen_package_manager/.gitignore create mode 100644 packages/tizen_package_manager/CHANGELOG.md create mode 100644 packages/tizen_package_manager/LICENSE create mode 100644 packages/tizen_package_manager/README.md create mode 100644 packages/tizen_package_manager/example/.gitignore create mode 100644 packages/tizen_package_manager/example/README.md create mode 100644 packages/tizen_package_manager/example/integration_test/tizen_package_manager_test.dart create mode 100644 packages/tizen_package_manager/example/lib/main.dart create mode 100644 packages/tizen_package_manager/example/pubspec.yaml create mode 100644 packages/tizen_package_manager/example/test_driver/integration_test.dart create mode 100644 packages/tizen_package_manager/example/tizen/.gitignore create mode 100644 packages/tizen_package_manager/example/tizen/App.cs create mode 100644 packages/tizen_package_manager/example/tizen/Runner.csproj create mode 100644 packages/tizen_package_manager/example/tizen/shared/res/ic_launcher.png create mode 100644 packages/tizen_package_manager/example/tizen/shared/res/org.example.simplehome.tpk create mode 100644 packages/tizen_package_manager/example/tizen/tizen-manifest.xml create mode 100644 packages/tizen_package_manager/lib/package_manager.dart create mode 100644 packages/tizen_package_manager/pubspec.yaml create mode 100644 packages/tizen_package_manager/tizen/.gitignore create mode 100644 packages/tizen_package_manager/tizen/inc/tizen_package_manager_plugin.h create mode 100644 packages/tizen_package_manager/tizen/project_def.prop create mode 100644 packages/tizen_package_manager/tizen/src/log.h create mode 100644 packages/tizen_package_manager/tizen/src/package_manager_utils.cc create mode 100644 packages/tizen_package_manager/tizen/src/package_manager_utils.h create mode 100644 packages/tizen_package_manager/tizen/src/tizen_package_manager_plugin.cc diff --git a/.github/labeler.yml b/.github/labeler.yml index dbb0844b6..c600ee321 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -41,6 +41,8 @@ - packages/tizen_app_manager/**/* 'p: tizen_audio_manager': - packages/tizen_audio_manager/**/* +'p: tizen_package_manager': + - packages/tizen_package_manager/**/* 'p: url_launcher': - packages/url_launcher/**/* 'p: video_player': diff --git a/.github/recipe.yaml b/.github/recipe.yaml index 4b70f85d4..ae13d4c7a 100644 --- a/.github/recipe.yaml +++ b/.github/recipe.yaml @@ -14,6 +14,7 @@ plugins: sqflite: ["wearable-5.5", "tv-6.0"] tizen_app_manager: ["wearable-5.5", "tv-6.0"] tizen_audio_manager: ["wearable-5.5", "tv-6.0"] + tizen_package_manager: ["wearable-5.5", "tv-6.0"] wakelock: ["wearable-5.5"] # Not supported by emulators. diff --git a/README.md b/README.md index e35d4d7cf..44a5772b3 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ The _"non-endorsed"_ status means that the plugin is not endorsed by the origina | [**tizen_app_manager**](packages/tizen_app_manager) | (Tizen-only) | [![pub package](https://img.shields.io/pub/v/tizen_app_manager.svg)](https://pub.dev/packages/tizen_app_manager) | N/A | | [**tizen_audio_manager**](packages/tizen_audio_manager) | (Tizen-only) | [![pub package](https://img.shields.io/pub/v/tizen_audio_manager.svg)](https://pub.dev/packages/tizen_audio_manager) | N/A | | [**tizen_notification**](packages/tizen_notification) | (Tizen-only) | [![pub package](https://img.shields.io/pub/v/tizen_notification.svg)](https://pub.dev/packages/tizen_notification) | N/A | +| [**tizen_package_manager**](packages/tizen_package_manager) | (Tizen-only) | [![pub package](https://img.shields.io/pub/v/tizen_package_manager.svg)](https://pub.dev/packages/tizen_package_manager) | 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 | @@ -70,6 +71,7 @@ The _"non-endorsed"_ status means that the plugin is not endorsed by the origina | [**tizen_app_manager**](packages/tizen_app_manager) | ✔️ | ✔️ | ✔️ | ✔️ | | [**tizen_audio_manager**](packages/tizen_audio_manager) | ✔️ | ✔️ | ✔️ | ✔️ | | [**tizen_notification**](packages/tizen_notification) | ❌ | ✔️ | ✔️ | ✔️ | API not supported | +| [**tizen_package_manager**](packages/tizen_package_manager) | ✔️ | ✔️ | ✔️ | ✔️ | | [**url_launcher_tizen**](packages/url_launcher) | ✔️ | ❌ | ✔️ | ❌ | No browser app | | [**video_player_tizen**](packages/video_player) | ✔️ | ✔️ | ⚠️ | ❌ | Functional limitations (see README)
TV emulator issue | | [**wakelock_tizen**](packages/wakelock) | ✔️ | ✔️ | ❌ | ❌ | Cannot override system display setting | diff --git a/packages/tizen_package_manager/.gitignore b/packages/tizen_package_manager/.gitignore new file mode 100644 index 000000000..e9dc58d3d --- /dev/null +++ b/packages/tizen_package_manager/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ + +build/ diff --git a/packages/tizen_package_manager/CHANGELOG.md b/packages/tizen_package_manager/CHANGELOG.md new file mode 100644 index 000000000..802b87e00 --- /dev/null +++ b/packages/tizen_package_manager/CHANGELOG.md @@ -0,0 +1,4 @@ +## 0.1.0 + +* Initial release. + diff --git a/packages/tizen_package_manager/LICENSE b/packages/tizen_package_manager/LICENSE new file mode 100644 index 000000000..487f7b1dc --- /dev/null +++ b/packages/tizen_package_manager/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2022 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 names of the copyright holders 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_package_manager/README.md b/packages/tizen_package_manager/README.md new file mode 100644 index 000000000..ce36899b8 --- /dev/null +++ b/packages/tizen_package_manager/README.md @@ -0,0 +1,58 @@ +# tizen_package_manager + + [![pub package](https://img.shields.io/pub/v/tizen_package_manager.svg)](https://pub.dev/packages/tizen_package_manager) + +Tizen package manager API. Used for getting installed package info. + +## Usage + +To use this package, add `tizen_package_manager` as a dependency in your `pubspec.yaml` file. + +```yaml +dependencies: + tizen_package_manager: ^0.1.0 +``` + +### Retrieving specific package info + +To retrieve information of a specific package, use the `getPackageInfo` method which returns an instance of `PackageInfo`. + +```dart +var packageId = 'org.tizen.settings'; +var packageInfo = await PackageManager.getPackageInfo(packageId); +``` + +### Retrieving all packages' info + +To retrieve information of all packages installed on a Tizen device, use `getPackagesInfo` method. + +```dart +var packageList = await PackageManager.getPackagesInfo(); +for (var package in packageList) { + // Handle each package's info. +} +``` + +### Monitoring package events + +You can listen for package events using `onInstallProgressChanged`, `onUninstallProgressChanged`, and `onUpdateProgressChanged`. + +```dart +_subscription = PackageManager.onInstallProgressChanged.listen((event) { + // A package is being installed. +}); +... +_subscription.cancel(); +``` + +## Required privileges + +Privileges are required to use the package manager functionality. Add required privileges in tizen-manifest.xml of your application. + +```xml + + http://tizen.org/privilege/packagemanager.info + + http://tizen.org/privilege/packagemanager.admin + +``` diff --git a/packages/tizen_package_manager/example/.gitignore b/packages/tizen_package_manager/example/.gitignore new file mode 100644 index 000000000..0fa6b675c --- /dev/null +++ b/packages/tizen_package_manager/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_package_manager/example/README.md b/packages/tizen_package_manager/example/README.md new file mode 100644 index 000000000..480068aea --- /dev/null +++ b/packages/tizen_package_manager/example/README.md @@ -0,0 +1,7 @@ +# tizen_package_manager_example + +Demonstrates how to use the tizen_package_manager 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_package_manager/example/integration_test/tizen_package_manager_test.dart b/packages/tizen_package_manager/example/integration_test/tizen_package_manager_test.dart new file mode 100644 index 000000000..44219cb4f --- /dev/null +++ b/packages/tizen_package_manager/example/integration_test/tizen_package_manager_test.dart @@ -0,0 +1,32 @@ +// Copyright 2022 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 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:tizen_package_manager/package_manager.dart'; + +import 'package:tizen_package_manager_example/main.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('Can get current package info', (WidgetTester tester) async { + // These test are based on the example app. + final PackageInfo info = + await PackageManager.getPackageInfo(currentPackageId); + expect(info.packageId, 'com.example.tizen_package_manager_example'); + expect(info.label, 'tizen_package_manager_example'); + expect(info.packageType, PackageType.tpk); + expect(info.version, '1.0.0'); + expect(info.isPreloaded, false); + expect(info.isSystem, false); + expect(info.isRemovable, true); + }); + + testWidgets('Can get all installed packages info', + (WidgetTester tester) async { + final List infos = await PackageManager.getPackagesInfo(); + expect(infos.isNotEmpty, true); + }); +} diff --git a/packages/tizen_package_manager/example/lib/main.dart b/packages/tizen_package_manager/example/lib/main.dart new file mode 100644 index 000000000..20c929946 --- /dev/null +++ b/packages/tizen_package_manager/example/lib/main.dart @@ -0,0 +1,188 @@ +// Copyright 2022 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 'package:flutter/material.dart'; +import 'package:tizen_package_manager/package_manager.dart'; + +/// The example app package ID. +const String currentPackageId = 'com.example.tizen_package_manager_example'; + +/// The main entry point for the UI app. +void main() { + runApp(const MyApp()); +} + +/// The main UI app widget. +class MyApp extends StatelessWidget { + /// The constructor of the main UI app widget. + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Package manager demo', + theme: ThemeData(primarySwatch: Colors.blue), + home: const _MyHomePage(), + ); + } +} + +class _MyHomePage extends StatefulWidget { + const _MyHomePage({Key? key}) : super(key: key); + + @override + _MyHomePageState createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State<_MyHomePage> { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Package manager demo')), + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => + _CurrentPackageInfoScreen(), + ), + ); + }, + child: const Text('Current app package info'), + ), + TextButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => _PackagesListScreen(), + ), + ); + }, + child: const Text('Installed packages list'), + ), + ], + ), + ), + ); + } +} + +class _CurrentPackageInfoScreen extends StatefulWidget { + @override + _CurrentPackageInfoScreenState createState() => + _CurrentPackageInfoScreenState(); +} + +class _CurrentPackageInfoScreenState extends State<_CurrentPackageInfoScreen> { + PackageInfo _packageInfo = PackageInfo( + packageId: '', + label: '', + packageType: PackageType.unknown, + iconPath: '', + version: '', + installedStorageType: '', + isSystem: false, + isPreloaded: false, + isRemovable: false, + ); + + @override + void initState() { + super.initState(); + + PackageManager.getPackageInfo(currentPackageId).then( + (PackageInfo packageInfo) { + setState(() { + _packageInfo = packageInfo; + }); + }, + ); + } + + Widget _infoTile(String title, String subtitle) { + return ListTile( + title: Text(title), + subtitle: Text(subtitle.isNotEmpty ? subtitle : 'Not set'), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Current app package info')), + body: ListView( + children: [ + _infoTile('Package ID', _packageInfo.packageId), + _infoTile('Label', _packageInfo.label), + _infoTile('Version', _packageInfo.version), + _infoTile('Package type', _packageInfo.packageType.name), + _infoTile('Icon path', _packageInfo.iconPath ?? ''), + _infoTile('Is system app', _packageInfo.isSystem.toString()), + _infoTile('Is preloaded app', _packageInfo.isPreloaded.toString()), + _infoTile('Is removable', _packageInfo.isRemovable.toString()), + ], + ), + ); + } +} + +class _PackagesListScreen extends StatefulWidget { + @override + _PackagesListScreenState createState() => _PackagesListScreenState(); +} + +class _PackagesListScreenState extends State<_PackagesListScreen> { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Package list')), + body: _PackagesListScreenContent(key: GlobalKey()), + ); + } +} + +class _PackagesListScreenContent extends StatelessWidget { + const _PackagesListScreenContent({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return FutureBuilder>( + future: PackageManager.getPackagesInfo(), + builder: (BuildContext context, AsyncSnapshot> data) { + if (data.data == null) { + return const Center(child: CircularProgressIndicator()); + } else { + final List packages = data.data!; + + return Scrollbar( + child: ListView.builder( + itemBuilder: (BuildContext context, int position) { + final PackageInfo package = packages[position]; + return Column( + children: [ + ListTile( + title: Text(package.label), + subtitle: Text('Package Id: ${package.packageId}\n' + 'Version: ${package.version}\n' + 'type: ${package.packageType}\n' + 'isSystem: ${package.isSystem}\n'), + ), + const Divider(height: 1.0) + ], + ); + }, + itemCount: packages.length, + ), + ); + } + }, + ); + } +} diff --git a/packages/tizen_package_manager/example/pubspec.yaml b/packages/tizen_package_manager/example/pubspec.yaml new file mode 100644 index 000000000..6db186ba4 --- /dev/null +++ b/packages/tizen_package_manager/example/pubspec.yaml @@ -0,0 +1,26 @@ +name: tizen_package_manager_example +description: Demonstrates how to use the tizen_package_manager plugin. +version: 1.0.0 +publish_to: 'none' + +environment: + sdk: ">=2.14.1 <3.0.0" + +dependencies: + flutter: + sdk: flutter + tizen_package_manager: + path: ../ + +dev_dependencies: + flutter_driver: + sdk: flutter + flutter_test: + sdk: flutter + integration_test: + sdk: flutter + integration_test_tizen: + path: ../../integration_test/ + +flutter: + uses-material-design: true diff --git a/packages/tizen_package_manager/example/test_driver/integration_test.dart b/packages/tizen_package_manager/example/test_driver/integration_test.dart new file mode 100644 index 000000000..b38629cca --- /dev/null +++ b/packages/tizen_package_manager/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_package_manager/example/tizen/.gitignore b/packages/tizen_package_manager/example/tizen/.gitignore new file mode 100644 index 000000000..750f3af1b --- /dev/null +++ b/packages/tizen_package_manager/example/tizen/.gitignore @@ -0,0 +1,5 @@ +flutter/ +.vs/ +*.user +bin/ +obj/ diff --git a/packages/tizen_package_manager/example/tizen/App.cs b/packages/tizen_package_manager/example/tizen/App.cs new file mode 100644 index 000000000..6dd4a6356 --- /dev/null +++ b/packages/tizen_package_manager/example/tizen/App.cs @@ -0,0 +1,20 @@ +using Tizen.Flutter.Embedding; + +namespace Runner +{ + public class App : FlutterApplication + { + protected override void OnCreate() + { + base.OnCreate(); + + GeneratedPluginRegistrant.RegisterPlugins(this); + } + + static void Main(string[] args) + { + var app = new App(); + app.Run(args); + } + } +} diff --git a/packages/tizen_package_manager/example/tizen/Runner.csproj b/packages/tizen_package_manager/example/tizen/Runner.csproj new file mode 100644 index 000000000..c3c43aed9 --- /dev/null +++ b/packages/tizen_package_manager/example/tizen/Runner.csproj @@ -0,0 +1,26 @@ + + + + Exe + tizen40 + + + + portable + + + none + + + + + + + + + + %(RecursiveDir) + + + + diff --git a/packages/tizen_package_manager/example/tizen/shared/res/ic_launcher.png b/packages/tizen_package_manager/example/tizen/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_package_manager/example/tizen/shared/res/org.example.simplehome.tpk b/packages/tizen_package_manager/example/tizen/shared/res/org.example.simplehome.tpk new file mode 100644 index 0000000000000000000000000000000000000000..658f2ad1a5780224658bc13ae9a1419c4fae01f3 GIT binary patch literal 39721 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%0D`Hg==Djb1@Gc`^2Bt?Kd8hf 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 KKL+;u-G2dxKlsi7 literal 0 HcmV?d00001 diff --git a/packages/tizen_package_manager/example/tizen/tizen-manifest.xml b/packages/tizen_package_manager/example/tizen/tizen-manifest.xml new file mode 100644 index 000000000..57965bb31 --- /dev/null +++ b/packages/tizen_package_manager/example/tizen/tizen-manifest.xml @@ -0,0 +1,13 @@ + + + + + + ic_launcher.png + + + + + http://tizen.org/privilege/packagemanager.info + + diff --git a/packages/tizen_package_manager/lib/package_manager.dart b/packages/tizen_package_manager/lib/package_manager.dart new file mode 100644 index 000000000..13b523c5a --- /dev/null +++ b/packages/tizen_package_manager/lib/package_manager.dart @@ -0,0 +1,304 @@ +// Copyright 2022 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'; + +/// Enumeration for the package type. +enum PackageType { + /// A special application package installed using the RPM spec. + /// Only some preloaded packages can have this type. + rpm, + + /// Tizen native application pacakge. + tpk, + + /// Tizen web/hybrid application package. + wgt, + + /// Unknown package. + unknown, +} + +/// Enumeration for the package manager event types. +enum PackageEventType { + /// Install event. + install, + + /// Uninstall event. + uninstall, + + /// Update event. + update, + + /// Move event. + move, + + /// Clear data event. + clearData, +} + +/// Enumeration for the package manager event state. +enum PackageEventState { + /// Processing started. + started, + + /// Processing state. + processing, + + /// Processing completed. + completed, + + /// Processing failed. + failed, +} + +/// The package manager provides information about installed packages. +/// This information includes the pacakge name, label, path of icon, version, +/// type and installed storage. +/// +/// For detailed information on Tizen's Package Manager, see: +/// https://docs.tizen.org/application/dotnet/guides/app-management/package-manager/ +class PackageManager { + PackageManager._(); + + static const MethodChannel _channel = MethodChannel('tizen/package_manager'); + + static const EventChannel _installEventChannel = + EventChannel('tizen/package_manager/install_event'); + + static const EventChannel _uninstallEventChannel = + EventChannel('tizen/package_manager/uninstall_event'); + + static const EventChannel _updateEventChannel = + EventChannel('tizen/package_manager/update_event'); + + /// Gets the package information for the given package ID. + static Future getPackageInfo(String packageId) async { + if (packageId.isEmpty) { + throw ArgumentError('Must not be empty', 'packageId'); + } + + final Map map = await _channel + .invokeMapMethod( + 'getPackage', {'packageId': packageId}) ?? + {}; + + return PackageInfo.fromMap(map); + } + + /// Retrieves the package information of all installed packages. + static Future> getPackagesInfo() async { + final List? packages = + await _channel.invokeMethod>('getPackages'); + + final List list = []; + if (packages != null) { + for (final dynamic package in packages) { + list.add(PackageInfo.fromMap(package)); + } + } + return list; + } + + /// Installs the package located at the given path. + /// + /// The `http://tizen.org/privilege/packagemanager.admin` platform privilege + /// is required to use this API. + static Future install(String packagePath) async { + if (packagePath.isEmpty) { + throw ArgumentError('Must not be empty', 'packagePath'); + } + + final bool ret = await _channel.invokeMethod( + 'install', {'path': packagePath}) ?? + false; + return ret; + } + + /// Uninstalls the package with the given package ID. + /// + /// The `http://tizen.org/privilege/packagemanager.admin` platform privilege + /// is required to use this API. + static Future uninstall(String packageId) async { + if (packageId.isEmpty) { + throw ArgumentError('Must not be empty', 'packageId'); + } + + final bool ret = await _channel.invokeMethod( + 'uninstall', {'packageId': packageId}) ?? + false; + return ret; + } + + /// A stream of events occurring when a package is getting installed + /// and the progress of the request to the package manager is changed. + static Stream get onInstallProgressChanged => + _installEventChannel + .receiveBroadcastStream() + .map((dynamic event) => PackageEvent.fromMap(event)); + + /// A stream of events occurring when a package is getting uninstalled + /// and the progress of the request to the package manager is changed. + static Stream get onUninstallProgressChanged => + _uninstallEventChannel + .receiveBroadcastStream() + .map((dynamic event) => PackageEvent.fromMap(event)); + + /// A stream of events occurring when a package is getting updated + /// and the progress of the request to the package manager is changed. + static Stream get onUpdateProgressChanged => _updateEventChannel + .receiveBroadcastStream() + .map((dynamic event) => PackageEvent.fromMap(event)); +} + +/// Represents information of specific package. +class PackageInfo { + /// Creates an instance of [PackageInfo] with the given parameters. + PackageInfo({ + required this.packageId, + required this.label, + required this.packageType, + required this.iconPath, + required this.version, + required this.installedStorageType, + required this.isSystem, + required this.isPreloaded, + required this.isRemovable, + }); + + /// The package ID. + final String packageId; + + /// Label of the package. + final String label; + + /// Type of the package. + final PackageType packageType; + + /// The path to the icon image. + final String? iconPath; + + /// Version of the package. + final String version; + + /// Installed storage type for the package. + /// the return value is either internal storage or external storage. + final String installedStorageType; + + /// Whether the package is a system package. + final bool isSystem; + + /// Whether the package is a preload package. + final bool isPreloaded; + + /// Whether the package is a removable package. + final bool isRemovable; + + /// Creates an instance of [PackageInfo] with the map parameter. + static PackageInfo fromMap(dynamic map) { + final Object? packageType = map['type']; + PackageType type = PackageType.unknown; + switch (packageType) { + case 'rpm': + type = PackageType.rpm; + break; + case 'wgt': + type = PackageType.wgt; + break; + case 'tpk': + type = PackageType.tpk; + break; + case 'unknown': + type = PackageType.unknown; + } + + return PackageInfo( + packageId: map['packageId'] as String, + label: map['label'] as String, + iconPath: map['iconPath'] as String?, + packageType: type, + version: map['version'] as String, + installedStorageType: map['installedStorageType'] as String, + isSystem: map['isSystem'] as bool, + isPreloaded: map['isPreloaded'] as bool, + isRemovable: map['isRemovable'] as bool, + ); + } +} + +/// Represents the event arguments of [PackageManager] events. +class PackageEvent { + /// Creates an instance of [PackageEvent] with the given parameters. + PackageEvent({ + required this.packageId, + required this.packageType, + required this.eventType, + required this.eventState, + required this.progress, + }); + + /// The package ID. + final String packageId; + + /// Type of the package. + final String packageType; + + /// The package manager event types + final PackageEventType eventType; + + /// The package manager event state. + final PackageEventState eventState; + + /// Progress for the request being processed by the package manager (in percent). + final int progress; + + /// Creates an instance of [PackageEvent] with the map parameter. + static PackageEvent fromMap(dynamic map) { + PackageEventType type = PackageEventType.install; + PackageEventState state = PackageEventState.started; + final String eventType = map['eventType'] as String; + final String eventState = map['eventState'] as String; + + switch (eventType) { + case 'install': + type = PackageEventType.install; + break; + case 'uninstall': + type = PackageEventType.uninstall; + break; + case 'update': + type = PackageEventType.update; + break; + case 'cleardata': + type = PackageEventType.clearData; + break; + case 'move': + type = PackageEventType.move; + } + + switch (eventState) { + case 'started': + state = PackageEventState.started; + break; + case 'processing': + state = PackageEventState.processing; + break; + case 'completed': + state = PackageEventState.completed; + break; + case 'failed': + state = PackageEventState.failed; + } + + return PackageEvent( + packageId: map['packageId'] as String, + packageType: map['type'] as String, + eventType: type, + eventState: state, + progress: map['progress'] as int, + ); + } +} diff --git a/packages/tizen_package_manager/pubspec.yaml b/packages/tizen_package_manager/pubspec.yaml new file mode 100644 index 000000000..34e594536 --- /dev/null +++ b/packages/tizen_package_manager/pubspec.yaml @@ -0,0 +1,21 @@ +name: tizen_package_manager +description: Tizen package manager APIs. Used to get detailed information on the installed packages on the Tizen device. +homepage: https://github.com/flutter-tizen/plugins +repository: https://github.com/flutter-tizen/plugins/tree/master/packages/tizen_package_manager +version: 0.1.0 + +environment: + sdk: ">=2.15.1 <3.0.0" + flutter: ">=2.5.0" + +dependencies: + flutter: + sdk: flutter + +flutter: + plugin: + platforms: + tizen: + pluginClass: TizenPackageManagerPlugin + fileName: tizen_package_manager_plugin.h + diff --git a/packages/tizen_package_manager/tizen/.gitignore b/packages/tizen_package_manager/tizen/.gitignore new file mode 100644 index 000000000..a2a7d62b1 --- /dev/null +++ b/packages/tizen_package_manager/tizen/.gitignore @@ -0,0 +1,5 @@ +.cproject +.sign +crash-info/ +Debug/ +Release/ diff --git a/packages/tizen_package_manager/tizen/inc/tizen_package_manager_plugin.h b/packages/tizen_package_manager/tizen/inc/tizen_package_manager_plugin.h new file mode 100644 index 000000000..da92b5561 --- /dev/null +++ b/packages/tizen_package_manager/tizen/inc/tizen_package_manager_plugin.h @@ -0,0 +1,27 @@ +// Copyright 2022 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. + +#ifndef FLUTTER_PLUGIN_TIZEN_PACKAGE_MANAGER_PLUGIN_H_ +#define FLUTTER_PLUGIN_TIZEN_PACKAGE_MANAGER_PLUGIN_H_ + +#include + +#ifdef FLUTTER_PLUGIN_IMPL +#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default"))) +#else +#define FLUTTER_PLUGIN_EXPORT +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +FLUTTER_PLUGIN_EXPORT void TizenPackageManagerPluginRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar); + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif // FLUTTER_PLUGIN_TIZEN_PACKAGE_MANAGER_PLUGIN_H_ diff --git a/packages/tizen_package_manager/tizen/project_def.prop b/packages/tizen_package_manager/tizen/project_def.prop new file mode 100644 index 000000000..06562b4d0 --- /dev/null +++ b/packages/tizen_package_manager/tizen/project_def.prop @@ -0,0 +1,24 @@ +# See https://docs.tizen.org/application/tizen-studio/native-tools/project-conversion +# for details. + +APPNAME = tizen_package_manager_plugin +type = staticLib +profile = common-4.0 + +# Source files +USER_SRCS += src/*.cc + +# User defines +USER_DEFS = +USER_UNDEFS = +USER_CPP_DEFS = FLUTTER_PLUGIN_IMPL +USER_CPP_UNDEFS = + +# Compiler flags +USER_CFLAGS_MISC = +USER_CPPFLAGS_MISC = + +# User includes +USER_INC_DIRS = inc src +USER_INC_FILES = +USER_CPP_INC_FILES = diff --git a/packages/tizen_package_manager/tizen/src/log.h b/packages/tizen_package_manager/tizen/src/log.h new file mode 100644 index 000000000..04abb8b4e --- /dev/null +++ b/packages/tizen_package_manager/tizen/src/log.h @@ -0,0 +1,24 @@ +#ifndef __LOG_H__ +#define __LOG_H__ + +#include + +#ifdef LOG_TAG +#undef LOG_TAG +#endif +#define LOG_TAG "TizenPackageManagerPlugin" + +#ifndef __MODULE__ +#define __MODULE__ strrchr("/" __FILE__, '/') + 1 +#endif + +#define LOG(prio, fmt, arg...) \ + dlog_print(prio, LOG_TAG, "%s: %s(%d) > " fmt, __MODULE__, __func__, \ + __LINE__, ##arg) + +#define LOG_DEBUG(fmt, args...) LOG(DLOG_DEBUG, fmt, ##args) +#define LOG_INFO(fmt, args...) LOG(DLOG_INFO, fmt, ##args) +#define LOG_WARN(fmt, args...) LOG(DLOG_WARN, fmt, ##args) +#define LOG_ERROR(fmt, args...) LOG(DLOG_ERROR, fmt, ##args) + +#endif // __LOG_H__ diff --git a/packages/tizen_package_manager/tizen/src/package_manager_utils.cc b/packages/tizen_package_manager/tizen/src/package_manager_utils.cc new file mode 100644 index 000000000..0654a4c7a --- /dev/null +++ b/packages/tizen_package_manager/tizen/src/package_manager_utils.cc @@ -0,0 +1,173 @@ +// Copyright 2022 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. + +#include "package_manager_utils.h" + +namespace package_manager_utils { + +bool ExtractValueFromMap(const flutter::EncodableValue &arguments, + const char *key, std::string &out_value) { + if (std::holds_alternative(arguments)) { + flutter::EncodableMap map = std::get(arguments); + auto iter = map.find(flutter::EncodableValue(key)); + if (iter != map.end() && !iter->second.IsNull()) { + if (auto pval = std::get_if(&iter->second)) { + out_value = *pval; + return true; + } + } + } + return false; +} + +int GetPackageData(package_info_h package_info, flutter::EncodableMap &value) { + char *pkg_name = nullptr; + char *label = nullptr; + char *type = nullptr; + char *icon_path = nullptr; + char *version = nullptr; + package_info_installed_storage_type_e installed_storage_type = + PACKAGE_INFO_INTERNAL_STORAGE; + bool is_system = false; + bool is_preloaded = false; + bool is_removable = true; + + int ret = package_info_get_package(package_info, &pkg_name); + if (ret != PACKAGE_MANAGER_ERROR_NONE || pkg_name == nullptr) { + LOG_ERROR("get package name error! : %s", get_error_message(ret)); + goto cleanup; + } + + ret = package_info_get_label(package_info, &label); + if (ret != PACKAGE_MANAGER_ERROR_NONE || label == nullptr) { + LOG_ERROR("get package label error! : %s", get_error_message(ret)); + goto cleanup; + } + + ret = package_info_get_type(package_info, &type); + if (ret != PACKAGE_MANAGER_ERROR_NONE || type == nullptr) { + LOG_ERROR("get package type error! : %s", get_error_message(ret)); + goto cleanup; + } + + ret = package_info_get_icon(package_info, &icon_path); + if (ret != PACKAGE_MANAGER_ERROR_NONE || icon_path == nullptr) { + // because some service app doesn't have icon, + // just print error log, and pass it + LOG_ERROR("get icon path error! : %s", get_error_message(ret)); + } + + ret = package_info_get_version(package_info, &version); + if (ret != PACKAGE_MANAGER_ERROR_NONE || version == nullptr) { + LOG_ERROR("get version error! : %s", get_error_message(ret)); + goto cleanup; + } + + ret = + package_info_get_installed_storage(package_info, &installed_storage_type); + if (ret != PACKAGE_MANAGER_ERROR_NONE) { + LOG_ERROR("get installed storage error! : %s", get_error_message(ret)); + goto cleanup; + } + + ret = package_info_is_system_package(package_info, &is_system); + if (ret != PACKAGE_MANAGER_ERROR_NONE) { + LOG_ERROR("check system package error! : %s", get_error_message(ret)); + goto cleanup; + } + + ret = package_info_is_preload_package(package_info, &is_preloaded); + if (ret != PACKAGE_MANAGER_ERROR_NONE) { + LOG_ERROR("check preload package error! : %s", get_error_message(ret)); + goto cleanup; + } + + ret = package_info_is_removable_package(package_info, &is_removable); + if (ret != PACKAGE_MANAGER_ERROR_NONE) { + LOG_ERROR("check removable package error! : %s", get_error_message(ret)); + goto cleanup; + } + + value[flutter::EncodableValue("packageId")] = + flutter::EncodableValue(std::string(pkg_name)); + value[flutter::EncodableValue("label")] = + flutter::EncodableValue(std::string(label)); + value[flutter::EncodableValue("type")] = + flutter::EncodableValue(std::string(type)); + value[flutter::EncodableValue("version")] = + flutter::EncodableValue(std::string(version)); + value[flutter::EncodableValue("installedStorageType")] = + flutter::EncodableValue(StorageTypeToString(installed_storage_type)); + value[flutter::EncodableValue("isSystem")] = + flutter::EncodableValue(is_system); + value[flutter::EncodableValue("isPreloaded")] = + flutter::EncodableValue(is_preloaded); + value[flutter::EncodableValue("isRemovable")] = + flutter::EncodableValue(is_removable); + if (icon_path) { + value[flutter::EncodableValue("iconPath")] = + flutter::EncodableValue(std::string(icon_path)); + } + +cleanup: + if (pkg_name) { + free(pkg_name); + } + if (label) { + free(label); + } + if (type) { + free(type); + } + if (icon_path) { + free(icon_path); + } + if (version) { + free(version); + } + + return ret; +} + +std::string StorageTypeToString(package_info_installed_storage_type_e value) { + switch (value) { + case PACKAGE_INFO_EXTERNAL_STORAGE: + return "External storage"; + case PACKAGE_INFO_INTERNAL_STORAGE: + default: + return "Internal storage"; + } +} + +std::string PacakgeEventTypeToString(package_manager_event_type_e type) { + switch (type) { + case PACKAGE_MANAGER_EVENT_TYPE_UNINSTALL: + return "uninstall"; + case PACKAGE_MANAGER_EVENT_TYPE_UPDATE: + return "update"; + case PACKAGE_MANAGER_EVENT_TYPE_MOVE: + return "move"; + case PACKAGE_MANAGER_EVENT_TYPE_CLEAR: + return "cleardata"; + case PACKAGE_MANAGER_EVENT_TYPE_INSTALL: + default: + return "install"; + } +} + +std::string PacakgeEventStateToString(package_manager_event_state_e state) { + switch (state) { + case PACKAGE_MANAGER_EVENT_STATE_STARTED: + return "started"; + case PACKAGE_MANAGER_EVENT_STATE_PROCESSING: + return "processing"; + case PACKAGE_MANAGER_EVENT_STATE_FAILED: + return "failed"; + case PACKAGE_MANAGER_EVENT_STATE_COMPLETED: + default: + return "completed"; + } +} + +} // namespace package_manager_utils diff --git a/packages/tizen_package_manager/tizen/src/package_manager_utils.h b/packages/tizen_package_manager/tizen/src/package_manager_utils.h new file mode 100644 index 000000000..4d3b594f6 --- /dev/null +++ b/packages/tizen_package_manager/tizen/src/package_manager_utils.h @@ -0,0 +1,30 @@ +// Copyright 2022 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. + +#ifndef FLUTTER_PLUGIN_PACKAGE_MANAGER_UTILS_H_ +#define FLUTTER_PLUGIN_PACKAGE_MANAGER_UTILS_H_ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "log.h" + +namespace package_manager_utils { +bool ExtractValueFromMap(const flutter::EncodableValue &arguments, + const char *key, std::string &out_value); +int GetPackageData(package_info_h package_info, flutter::EncodableMap &value); + +std::string StorageTypeToString(package_info_installed_storage_type_e value); +std::string PacakgeEventTypeToString(package_manager_event_type_e type); +std::string PacakgeEventStateToString(package_manager_event_state_e state); +} // namespace package_manager_utils + +#endif diff --git a/packages/tizen_package_manager/tizen/src/tizen_package_manager_plugin.cc b/packages/tizen_package_manager/tizen/src/tizen_package_manager_plugin.cc new file mode 100644 index 000000000..36f5e5268 --- /dev/null +++ b/packages/tizen_package_manager/tizen/src/tizen_package_manager_plugin.cc @@ -0,0 +1,421 @@ +// Copyright 2022 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. + +#include "tizen_package_manager_plugin.h" + +#include +#include +#include + +#include "package_manager_utils.h" + +const char kPackageTypeUnkown[] = "unknown"; +const char kPackageTypeTpk[] = "tpk"; +const char kPackageTypeWgt[] = "wgt"; + +class TizenPackageManagerPlugin : public flutter::Plugin { + public: + using MethodResultPtr = + std::unique_ptr>; + + static void RegisterWithRegistrar(flutter::PluginRegistrar *registrar) { + auto plugin = std::make_unique(); + plugin->SetupChannels(registrar); + registrar->AddPlugin(std::move(plugin)); + } + + TizenPackageManagerPlugin() {} + + virtual ~TizenPackageManagerPlugin() { UnregisterObserver(); } + + private: + void HandleMethodCall( + const flutter::MethodCall &method_call, + MethodResultPtr result) { + const auto &arguments = *method_call.arguments(); + + if (method_call.method_name().compare("getPackage") == 0) { + GetPackageInfo(arguments, std::move(result)); + } else if (method_call.method_name().compare("getPackages") == 0) { + GetAllPackagesInfo(std::move(result)); + } else if (method_call.method_name().compare("install") == 0) { + Install(arguments, std::move(result)); + } else if (method_call.method_name().compare("uninstall") == 0) { + Uninstall(arguments, std::move(result)); + } else { + result->NotImplemented(); + } + } + + void RegisterObserver( + std::unique_ptr> &&events) { + int ret = PACKAGE_MANAGER_ERROR_NONE; + LOG_INFO("RegisterObserver"); + + if (package_manager_h_ == nullptr) { + ret = package_manager_create(&package_manager_h_); + if (ret != PACKAGE_MANAGER_ERROR_NONE) { + char *err_msg = get_error_message(ret); + LOG_ERROR("Failed package_manager_create : %s", err_msg); + events->Error("Failed to create package manager handle", + std::string(err_msg)); + return; + } + } + + package_manager_set_event_status(package_manager_h_, + PACKAGE_MANAGER_STATUS_TYPE_INSTALL | + PACKAGE_MANAGER_STATUS_TYPE_UNINSTALL | + PACKAGE_MANAGER_STATUS_TYPE_UPGRADE); + ret = package_manager_set_event_cb(package_manager_h_, PackageEventCB, + (void *)this); + if (ret != PACKAGE_MANAGER_ERROR_NONE) { + char *err_msg = get_error_message(ret); + LOG_ERROR("Failed package_manager_set_event_cb : %s", err_msg); + events->Error("Failed to add callback", std::string(err_msg)); + return; + } + is_event_callback_registered_ = true; + } + + void UnregisterObserver() { + LOG_INFO("UnregisterObserver"); + if (is_event_callback_registered_ && package_manager_h_) { + int ret = package_manager_unset_event_cb(package_manager_h_); + if (ret != PACKAGE_MANAGER_ERROR_NONE) { + LOG_ERROR("Failed package_manager_unset_event_cb : %s", + get_error_message(ret)); + } + + package_manager_destroy(package_manager_h_); + package_manager_h_ = nullptr; + is_event_callback_registered_ = false; + } + install_events_ = nullptr; + uninstall_events_ = nullptr; + update_events_ = nullptr; + } + + void GetPackageInfo(const flutter::EncodableValue &arguments, + MethodResultPtr result) { + std::string id = ""; + const char *package_id; + char *err_msg; + package_info_h package_info = nullptr; + flutter::EncodableMap value; + + if (!package_manager_utils::ExtractValueFromMap(arguments, "packageId", + id)) { + result->Error("InvalidArguments", "Please check packageId"); + return; + } + package_id = id.c_str(); + LOG_INFO("GetPackageInfo() package_id : %s", package_id); + + int ret = package_info_create(package_id, &package_info); + if (ret != PACKAGE_MANAGER_ERROR_NONE || package_info == nullptr) { + err_msg = get_error_message(ret); + LOG_ERROR("Failed to get package_info handler : %s", err_msg); + result->Error(std::to_string(ret), + "Failed to create package_info handler.", + flutter::EncodableValue(std::string(err_msg))); + goto cleanup; + } + + ret = package_manager_utils::GetPackageData(package_info, value); + if (ret != PACKAGE_MANAGER_ERROR_NONE) { + err_msg = get_error_message(ret); + result->Error(std::to_string(ret), "Failed to package info.", + flutter::EncodableValue(std::string(err_msg))); + goto cleanup; + } + result->Success(flutter::EncodableValue(value)); + + cleanup: + if (package_info) { + package_info_destroy(package_info); + } + } + + void GetAllPackagesInfo(MethodResultPtr result) { + LOG_INFO("GetAllPackagesInfo()"); + packages_.erase(packages_.begin(), packages_.end()); + int ret = package_manager_foreach_package_info(PackageInfoCB, (void *)this); + if (ret != PACKAGE_MANAGER_ERROR_NONE) { + char *err_msg = get_error_message(ret); + LOG_ERROR("package_manager_foreach_package_info error: %s", err_msg); + result->Error(std::to_string(ret), + "package_manager_foreach_package_info error.", + flutter::EncodableValue(std::string(err_msg))); + } + result->Success(flutter::EncodableValue(packages_)); + } + + void Install(const flutter::EncodableValue &arguments, + MethodResultPtr result) { + std::string path = ""; + const char *package_path; + char *err_msg; + package_manager_request_h package_manager_request = nullptr; + int request_id; + + if (!package_manager_utils::ExtractValueFromMap(arguments, "path", path)) { + result->Error("InvalidArguments", "Please check path"); + return; + } + package_path = path.c_str(); + LOG_INFO("Install() package_path : %s", package_path); + + int ret = package_manager_request_create(&package_manager_request); + if (ret != PACKAGE_MANAGER_ERROR_NONE || + package_manager_request == nullptr) { + err_msg = get_error_message(ret); + LOG_ERROR("Failed to get package_manager_request handler : %s", err_msg); + result->Error(std::to_string(ret), + "Failed to create package_manager_request handler.", + flutter::EncodableValue(std::string(err_msg))); + goto cleanup; + } + + ret = package_manager_request_install(package_manager_request, package_path, + &request_id); + if (ret != PACKAGE_MANAGER_ERROR_NONE) { + err_msg = get_error_message(ret); + result->Error(std::to_string(ret), "Failed to install.", + flutter::EncodableValue(std::string(err_msg))); + goto cleanup; + } + result->Success(flutter::EncodableValue(true)); + + cleanup: + if (package_manager_request) { + package_manager_request_destroy(package_manager_request); + } + } + + void Uninstall(const flutter::EncodableValue &arguments, + MethodResultPtr result) { + std::string id = ""; + const char *package_id; + char *err_msg; + package_manager_request_h package_manager_request = nullptr; + int request_id; + + if (!package_manager_utils::ExtractValueFromMap(arguments, "packageId", + id)) { + result->Error("InvalidArguments", "Please check packageId"); + return; + } + package_id = id.c_str(); + LOG_INFO("Uninstall() package_id : %s", package_id); + + int ret = package_manager_request_create(&package_manager_request); + if (ret != PACKAGE_MANAGER_ERROR_NONE || + package_manager_request == nullptr) { + err_msg = get_error_message(ret); + LOG_ERROR("Failed to get package_manager_request handler : %s", err_msg); + result->Error(std::to_string(ret), + "Failed to create package_manager_request handler.", + flutter::EncodableValue(std::string(err_msg))); + goto cleanup; + } + + ret = package_manager_request_set_type(package_manager_request, + kPackageTypeUnkown); + if (ret != PACKAGE_MANAGER_ERROR_NONE) { + err_msg = get_error_message(ret); + LOG_ERROR("Failed to set request type : %s", err_msg); + result->Error(std::to_string(ret), "Failed to set request type.", + flutter::EncodableValue(std::string(err_msg))); + goto cleanup; + } + + ret = package_manager_request_uninstall(package_manager_request, package_id, + &request_id); + if (ret != PACKAGE_MANAGER_ERROR_NONE) { + err_msg = get_error_message(ret); + result->Error(std::to_string(ret), "Failed to uninstall.", + flutter::EncodableValue(std::string(err_msg))); + goto cleanup; + } + result->Success(flutter::EncodableValue(true)); + + cleanup: + if (package_manager_request) { + package_manager_request_destroy(package_manager_request); + } + } + + void SetupChannels(flutter::PluginRegistrar *registrar) { + auto method_channel = + std::make_unique>( + registrar->messenger(), "tizen/package_manager", + &flutter::StandardMethodCodec::GetInstance()); + + method_channel->SetMethodCallHandler([this](const auto &call, auto result) { + this->HandleMethodCall(call, std::move(result)); + }); + + auto install_event_channel = + std::make_unique>( + registrar->messenger(), "tizen/package_manager/install_event", + &flutter::StandardMethodCodec::GetInstance()); + + auto uninstall_event_channel = + std::make_unique>( + registrar->messenger(), "tizen/package_manager/uninstall_event", + &flutter::StandardMethodCodec::GetInstance()); + + auto update_event_channel = + std::make_unique>( + registrar->messenger(), "tizen/package_manager/update_event", + &flutter::StandardMethodCodec::GetInstance()); + + auto install_event_channel_handler = + std::make_unique>( + [this](const flutter::EncodableValue *arguments, + std::unique_ptr> &&events) + -> std::unique_ptr> { + LOG_INFO("OnListen install"); + install_events_ = std::move(events); + if (registered_cnt_ == 0) { + this->RegisterObserver(std::move(events)); + } + registered_cnt_++; + return nullptr; + }, + [this](const flutter::EncodableValue *arguments) + -> std::unique_ptr> { + registered_cnt_--; + LOG_INFO("OnCancel install"); + if (registered_cnt_ == 0) { + this->UnregisterObserver(); + } + install_events_ = nullptr; + return nullptr; + }); + + auto uninstall_event_channel_handler = + std::make_unique>( + [this](const flutter::EncodableValue *arguments, + std::unique_ptr> &&events) + -> std::unique_ptr> { + LOG_INFO("OnListen uninstall"); + uninstall_events_ = std::move(events); + if (registered_cnt_ == 0) { + this->RegisterObserver(std::move(events)); + } + registered_cnt_++; + return nullptr; + }, + [this](const flutter::EncodableValue *arguments) + -> std::unique_ptr> { + LOG_INFO("OnCancel uninstall"); + registered_cnt_--; + if (registered_cnt_ == 0) { + this->UnregisterObserver(); + } + uninstall_events_ = nullptr; + return nullptr; + }); + + auto update_event_channel_handler = + std::make_unique>( + [this](const flutter::EncodableValue *arguments, + std::unique_ptr> &&events) + -> std::unique_ptr> { + LOG_INFO("OnListen update"); + update_events_ = std::move(events); + if (registered_cnt_ == 0) { + this->RegisterObserver(std::move(events)); + } + registered_cnt_++; + return nullptr; + }, + [this](const flutter::EncodableValue *arguments) + -> std::unique_ptr> { + LOG_INFO("OnCancel update"); + registered_cnt_--; + if (registered_cnt_ == 0) { + this->UnregisterObserver(); + } + update_events_ = nullptr; + return nullptr; + }); + + install_event_channel->SetStreamHandler( + std::move(install_event_channel_handler)); + uninstall_event_channel->SetStreamHandler( + std::move(uninstall_event_channel_handler)); + update_event_channel->SetStreamHandler( + std::move(update_event_channel_handler)); + } + + static bool PackageInfoCB(package_info_h package_info, void *user_data) { + if (package_info) { + TizenPackageManagerPlugin *plugin = + (TizenPackageManagerPlugin *)user_data; + flutter::EncodableMap value; + int ret = package_manager_utils::GetPackageData(package_info, value); + if (ret == PACKAGE_MANAGER_ERROR_NONE) { + plugin->packages_.push_back(flutter::EncodableValue(value)); + } + return true; + } + return false; + } + + static void PackageEventCB(const char *type, const char *package, + package_manager_event_type_e event_type, + package_manager_event_state_e event_state, + int progress, package_manager_error_e error, + void *user_data) { + LOG_INFO("PackageEventCB, packageId : %s, type: %s", package, type); + LOG_INFO( + "event_type: %s, event_state: %s, progress : %d ", + package_manager_utils::PacakgeEventTypeToString(event_type).c_str(), + package_manager_utils::PacakgeEventStateToString(event_state).c_str(), + progress); + + TizenPackageManagerPlugin *plugin = (TizenPackageManagerPlugin *)user_data; + flutter::EncodableMap msg; + msg[flutter::EncodableValue("packageId")] = + flutter::EncodableValue(std::string(package)); + msg[flutter::EncodableValue("type")] = + flutter::EncodableValue(std::string(type)); + msg[flutter::EncodableValue("eventType")] = flutter::EncodableValue( + package_manager_utils::PacakgeEventTypeToString(event_type)); + msg[flutter::EncodableValue("eventState")] = flutter::EncodableValue( + package_manager_utils::PacakgeEventStateToString(event_state)); + msg[flutter::EncodableValue("progress")] = + flutter::EncodableValue(progress); + + if (event_type == PACKAGE_MANAGER_EVENT_TYPE_INSTALL && + plugin->install_events_) { + plugin->install_events_->Success(flutter::EncodableValue(msg)); + } else if (event_type == PACKAGE_MANAGER_EVENT_TYPE_UNINSTALL && + plugin->uninstall_events_) { + plugin->uninstall_events_->Success(flutter::EncodableValue(msg)); + } else if (event_type == PACKAGE_MANAGER_EVENT_TYPE_UPDATE && + plugin->update_events_) { + plugin->update_events_->Success(flutter::EncodableValue(msg)); + } + } + + flutter::EncodableList packages_; + std::unique_ptr> install_events_; + std::unique_ptr> + uninstall_events_; + std::unique_ptr> update_events_; + bool is_event_callback_registered_ = false; + int registered_cnt_ = 0; + package_manager_h package_manager_h_ = nullptr; +}; + +void TizenPackageManagerPluginRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar) { + TizenPackageManagerPlugin::RegisterWithRegistrar( + flutter::PluginRegistrarManager::GetInstance() + ->GetRegistrar(registrar)); +}