From abfe7992f00ed2492da0c1eab2659b3cb764b4a0 Mon Sep 17 00:00:00 2001 From: faisalansari0367 <61263149+faisalansari0367@users.noreply.github.com> Date: Fri, 22 Jan 2021 17:13:46 +0530 Subject: [PATCH 1/3] create a new method to get app related info from .apk file and add an App class in utils so you can run it in a different isolate if you want --- .../fr/g123k/deviceapps/DeviceAppsPlugin.java | 72 +++++++++++++++++-- lib/device_apps.dart | 54 +++++++------- 2 files changed, 91 insertions(+), 35 deletions(-) diff --git a/android/src/main/java/fr/g123k/deviceapps/DeviceAppsPlugin.java b/android/src/main/java/fr/g123k/deviceapps/DeviceAppsPlugin.java index 43d77df..8551d0e 100644 --- a/android/src/main/java/fr/g123k/deviceapps/DeviceAppsPlugin.java +++ b/android/src/main/java/fr/g123k/deviceapps/DeviceAppsPlugin.java @@ -88,9 +88,33 @@ public void run() { } else { String packageName = call.argument("package_name").toString(); boolean includeAppIcon = call.hasArgument("include_app_icon") && (Boolean) (call.argument("include_app_icon")); + result.success(getApp(packageName, includeAppIcon)); } break; + + case "getAppByApkFiles": + // boolean includeAppIcons = call.hasArgument("include_app_icons") && (Boolean) (call.argument("include_app_icons")); + if (!call.hasArgument("paths")) { + result.error("ERROR", "Empty or invalid argument", null); + } else { + List apkFilePaths = call.argument("paths"); + boolean includeAppIcon = call.hasArgument("include_app_icon") && (Boolean) (call.argument("include_app_icon")); + // result.success(getAppByApkFile(apkFilePaths, includeAppIcon)); + fetchApkIconsByFile(apkFilePaths, includeAppIcon, new InstalledAppsCallback() { + @Override + public void onInstalledAppsListAvailable(final List> apps) { + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + result.success(apps); + } + }); + } + }); + } + break; + case "isAppInstalled": if (!call.hasArgument("package_name") || TextUtils.isEmpty(call.argument("package_name").toString())) { result.error("ERROR", "Empty or null package name", null); @@ -127,6 +151,25 @@ public void run() { }); } + //thread for getting apkIcons + + private void fetchApkIconsByFile(final List listOfPathsOfApkFiles, final boolean includeAppIcons, final InstalledAppsCallback callback) { + asyncWork.run(new Runnable() { + + @Override + public void run() { + List> installedApps = getAppByApkFile(listOfPathsOfApkFiles, includeAppIcons); + + if (callback != null) { + callback.onInstalledAppsListAvailable(installedApps); + } + } + + }); + } + + + private List> getInstalledApps(boolean includeSystemApps, boolean includeAppIcons, boolean onlyAppsWithLaunchIntent) { PackageManager packageManager = context.getPackageManager(); List apps = packageManager.getInstalledPackages(0); @@ -136,6 +179,7 @@ private List> getInstalledApps(boolean includeSystemApps, bo if (!includeSystemApps && isSystemApp(pInfo)) { continue; } + if (onlyAppsWithLaunchIntent && packageManager.getLaunchIntentForPackage(pInfo.packageName) == null) { continue; } @@ -149,13 +193,11 @@ private List> getInstalledApps(boolean includeSystemApps, bo private boolean openApp(@NonNull String packageName) { Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName); - // Null pointer check in case package name was not found if (launchIntent != null) { context.startActivity(launchIntent); return true; } - return false; } @@ -181,6 +223,27 @@ private Map getApp(String packageName, boolean includeAppIcon) { } } + + // I am doing my stuff here... + // i want to get apkInfo from an apk file.. + private List> getAppByApkFile(List listOfPathsOfApkFiles,boolean includeAppIcon) { + PackageManager packageManager = context.getPackageManager(); + List> apkFiles = new ArrayList<>(listOfPathsOfApkFiles.size()); + + for(String file : listOfPathsOfApkFiles) { + PackageInfo pi = packageManager.getPackageArchiveInfo(file, 0); + + if(pi != null) { + pi.applicationInfo.sourceDir = file; + pi.applicationInfo.publicSourceDir = file; + Map map = getAppData(packageManager, pi, includeAppIcon); + apkFiles.add(map); + } + } + + return apkFiles; + } + private Map getAppData(PackageManager packageManager, PackageInfo pInfo, boolean includeAppIcon) { Map map = new HashMap<>(); map.put("app_name", pInfo.applicationInfo.loadLabel(packageManager).toString()); @@ -198,12 +261,9 @@ private Map getAppData(PackageManager packageManager, PackageInf } if (includeAppIcon) { - try { - Drawable icon = packageManager.getApplicationIcon(pInfo.packageName); + Drawable icon = pInfo.applicationInfo.loadIcon(packageManager); String encodedImage = encodeToBase64(getBitmapFromDrawable(icon), Bitmap.CompressFormat.PNG, 100); map.put("app_icon", encodedImage); - } catch (PackageManager.NameNotFoundException ignored) { - } } return map; diff --git a/lib/device_apps.dart b/lib/device_apps.dart index 18b41f9..52dfe60 100644 --- a/lib/device_apps.dart +++ b/lib/device_apps.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; +import 'package:device_apps/app_utils.dart'; import 'package:flutter/services.dart'; /// Plugin to list applications installed on an Android device @@ -17,39 +18,23 @@ class DeviceApps { /// To get the icon you have to cast the object to [ApplicationWithIcon] /// [onlyAppsWithLaunchIntent] will only list applications when an entrypoint. /// It is similar to what a launcher will display + // + static Future> getInstalledApplications( {bool includeSystemApps: false, bool includeAppIcons: false, bool onlyAppsWithLaunchIntent: false}) async { - return _channel.invokeMethod('getInstalledApps', { - 'system_apps': includeSystemApps, - 'include_app_icons': includeAppIcons, - 'only_apps_with_launch_intent': onlyAppsWithLaunchIntent - }).then((Object apps) { - if (apps != null && apps is List) { - List list = List(); - for (Object app in apps) { - if (app is Map) { - try { - list.add(Application._(app)); - } catch (e) { - if (e is AssertionError) { - print('[DeviceApps] Unable to add the following app: $app'); - } else { - print('[DeviceApps] $e'); - } - } - } - } - - return list; - } else { - return List(0); - } - }).catchError((Object err) { - print(err); - return List(0); - }); + try { + final List data = + await _channel.invokeMethod('getInstalledApps', { + 'system_apps': includeSystemApps, + 'include_app_icons': includeAppIcons, + 'only_apps_with_launch_intent': onlyAppsWithLaunchIntent + }); + return data; + } catch (e) { + throw Exception(e); + } } /// Provide all information for a given app by its [packageName] @@ -98,6 +83,17 @@ class DeviceApps { return await _channel .invokeMethod('openApp', {'package_name': packageName}); } + + // This return List> you have a util class in this package called App which converts this List to List you just need to do + // App.fromList(List list); + // that's it. + // here we are not doing this because if you want to parse this into a diffrent isolate you can do that. + // this is the benefit of not doing it here. + static Future getAppByApkFile(List list) async { + final List data = await _channel.invokeMethod( + 'getAppByApkFiles', >{'paths': list}); + return data; + } } /// An application installed on the device From f9b311d20c1d89e4d9ba26882bb8d3dde3d84e8c Mon Sep 17 00:00:00 2001 From: faisalansari0367 <61263149+faisalansari0367@users.noreply.github.com> Date: Fri, 22 Jan 2021 17:27:16 +0530 Subject: [PATCH 2/3] Added getAppByApkFile Added a method to to get application info by an .apk file and added an App class to convert List to List --- .../ios/Flutter/flutter_export_environment.sh | 15 ++ lib/app_utils.dart | 146 ++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 example/ios/Flutter/flutter_export_environment.sh create mode 100644 lib/app_utils.dart diff --git a/example/ios/Flutter/flutter_export_environment.sh b/example/ios/Flutter/flutter_export_environment.sh new file mode 100644 index 0000000..d0d5ddc --- /dev/null +++ b/example/ios/Flutter/flutter_export_environment.sh @@ -0,0 +1,15 @@ +#!/bin/sh +# This is a generated file; do not edit or check into version control. +export "FLUTTER_ROOT=E:\AndroidStudio\flutter" +export "FLUTTER_APPLICATION_PATH=C:\Users\FAISAL\Documents\GitHub\flutter_plugin_device_apps\example" +export "FLUTTER_TARGET=lib\main.dart" +export "FLUTTER_BUILD_DIR=build" +export "SYMROOT=${SOURCE_ROOT}/../build\ios" +export "OTHER_LDFLAGS=$(inherited) -framework Flutter" +export "FLUTTER_FRAMEWORK_DIR=E:\AndroidStudio\flutter\bin\cache\artifacts\engine\ios" +export "FLUTTER_BUILD_NAME=1.0.0" +export "FLUTTER_BUILD_NUMBER=1" +export "DART_OBFUSCATION=false" +export "TRACK_WIDGET_CREATION=false" +export "TREE_SHAKE_ICONS=false" +export "PACKAGE_CONFIG=.packages" diff --git a/lib/app_utils.dart b/lib/app_utils.dart new file mode 100644 index 0000000..affea4f --- /dev/null +++ b/lib/app_utils.dart @@ -0,0 +1,146 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +enum ApplicationCategory { + /// Category for apps which primarily work with audio or music, such as + /// music players. + audio, + + /// Category for apps which are primarily games. + game, + + /// Category for apps which primarily work with images or photos, such as + /// camera or gallery apps. + image, + + /// Category for apps which are primarily maps apps, such as navigation apps. + maps, + + /// Category for apps which are primarily news apps, such as newspapers, + /// magazines, or sports apps. + news, + + /// Category for apps which are primarily productivity apps, such as cloud + /// storage or workplace apps. + productivity, + + /// Category for apps which are primarily social apps, such as messaging, + /// communication, email, or social network apps. + social, + + /// Category for apps which primarily work with video or movies, such as + /// streaming video apps. + video, + + /// Value when category is undefined. + undefined +} + +class App { + final String appName; + final String apkFilePath; + final String packageName; + final String versionName; + final int versionCode; + final String dataDir; + final bool systemApp; + final int installTimeMillis; + final int updateTimeMillis; + final Uint8List appIcon; + final ApplicationCategory category; + + App({ + this.appIcon, + this.appName, + this.apkFilePath, + this.packageName, + this.versionName, + this.versionCode, + this.dataDir, + this.systemApp, + this.installTimeMillis, + this.updateTimeMillis, + this.category, + }); + + static List fromList(List> list) { + return List.generate(list.length, (int i) { + final Map map = list[i]; + assert(map['app_name'] != null); + assert(map['apk_file_path'] != null); + assert(map['package_name'] != null); + assert(map['version_name'] != null); + assert(map['version_code'] != null); + assert(map['system_app'] != null); + assert(map['install_time'] != null); + assert(map['update_time'] != null); + if (map['app_icon'] != null) { + return App( + appName: map['app_name'], + apkFilePath: map['apk_file_path'], + packageName: map['package_name'], + versionName: map['version_name'], + versionCode: map['version_code'], + dataDir: map['data_dir'], + systemApp: map['system_app'], + installTimeMillis: map['install_time'], + updateTimeMillis: map['update_time'], + appIcon: base64Decode(map['app_icon']), + category: _parseCategory(map['category'])); + } else { + return App( + appName: map['app_name'], + apkFilePath: map['apk_file_path'], + packageName: map['package_name'], + versionName: map['version_name'], + versionCode: map['version_code'], + dataDir: map['data_dir'], + systemApp: map['system_app'], + installTimeMillis: map['install_time'], + updateTimeMillis: map['update_time'], + category: _parseCategory(map['category']) + // appIcon: base64Decode(map['app_icon']), + ); + } + }); + } + + static ApplicationCategory _parseCategory(Object category) { + if (category == null || (category is num && category < 0)) { + return ApplicationCategory.undefined; + } else if (category == 0) { + return ApplicationCategory.game; + } else if (category == 1) { + return ApplicationCategory.audio; + } else if (category == 2) { + return ApplicationCategory.video; + } else if (category == 3) { + return ApplicationCategory.image; + } else if (category == 4) { + return ApplicationCategory.social; + } else if (category == 5) { + return ApplicationCategory.news; + } else if (category == 6) { + return ApplicationCategory.maps; + } else if (category == 7) { + return ApplicationCategory.productivity; + } else { + return ApplicationCategory.undefined; + } + } + + @override + String toString() { + return 'Application{' + 'appName: $appName, ' + 'apkFilePath: $apkFilePath, ' + 'packageName: $packageName, ' + 'versionName: $versionName, ' + 'versionCode: $versionCode, ' + 'dataDir: $dataDir, ' + 'systemApp: $systemApp, ' + 'installTimeMillis: $installTimeMillis, ' + 'updateTimeMillis: $updateTimeMillis, ' + 'category: $category}'; + } +} From 99d992106854cdeb8981c16f065407b6ae3e1bb9 Mon Sep 17 00:00:00 2001 From: faisalansari0367 <61263149+faisalansari0367@users.noreply.github.com> Date: Fri, 22 Jan 2021 17:34:58 +0530 Subject: [PATCH 3/3] Update device_apps.dart --- lib/device_apps.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/device_apps.dart b/lib/device_apps.dart index 52dfe60..c15c774 100644 --- a/lib/device_apps.dart +++ b/lib/device_apps.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; -import 'package:device_apps/app_utils.dart'; import 'package:flutter/services.dart'; /// Plugin to list applications installed on an Android device