Skip to content

BREAKING/UNDO BREAKING Add support for excluding unlaunchable apps - as per pre-1.5.1 #43

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 1.6.0

* Fixed unresolved reference issue.
* Fixed random ANR

## 1.5.2

* Updated the compile SDK and Gradle versions.
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ possible.
``` dart
List<AppInfo> apps = await InstalledApps.getInstalledApps(
bool excludeSystemApps,
bool excludeUnlaunchable,
bool withIcon,
String packageNamePrefix
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,34 +19,21 @@ import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.PluginRegistry.Registrar
import java.util.Locale.ENGLISH

class InstalledAppsPlugin : MethodCallHandler, FlutterPlugin, ActivityAware {

class InstalledAppsPlugin() : MethodCallHandler, FlutterPlugin, ActivityAware {

companion object {

var context: Context? = null

@JvmStatic
fun registerWith(registrar: Registrar) {
context = registrar.context()
register(registrar.messenger())
}

@JvmStatic
fun register(messenger: BinaryMessenger) {
val channel = MethodChannel(messenger, "installed_apps")
channel.setMethodCallHandler(InstalledAppsPlugin())
}
}
private lateinit var channel: MethodChannel
private var context: Context? = null

override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
register(binding.binaryMessenger)
context = binding.applicationContext
channel = MethodChannel(binding.binaryMessenger, "installed_apps")
channel.setMethodCallHandler(this)
}

override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}

override fun onAttachedToActivity(activityPluginBinding: ActivityPluginBinding) {
Expand All @@ -63,54 +50,59 @@ class InstalledAppsPlugin() : MethodCallHandler, FlutterPlugin, ActivityAware {

override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
if (context == null) {
result.error("", "Something went wrong!", null)
result.error("ERROR", "Context is null", null)
return
}
when (call.method) {
"getInstalledApps" -> {
val includeSystemApps = call.argument("exclude_system_apps") ?: true
val withIcon = call.argument("with_icon") ?: false
val packageNamePrefix: String = call.argument("package_name_prefix") ?: ""
val excludeSystemApps = call.argument<Boolean>("exclude_system_apps") ?: true
val excludeLaunchableApps = call.argument("exclude_unlaunchable") ?: true
val withIcon = call.argument<Boolean>("with_icon") ?: false
val packageNamePrefix = call.argument<String>("package_name_prefix") ?: ""
val platformTypeName = call.argument<String>("platform_type") ?: ""

Thread {
val apps: List<Map<String, Any?>> =
getInstalledApps(includeSystemApps, withIcon, packageNamePrefix)
getInstalledApps(excludeSystemApps, excludeLaunchableApps, withIcon, packageNamePrefix, PlatformType.fromString(platformTypeName))
result.success(apps)
}.start()
}

"startApp" -> {
val packageName: String? = call.argument("package_name")
val packageName = call.argument<String>("package_name")
result.success(startApp(packageName))
}

"openSettings" -> {
val packageName: String? = call.argument("package_name")
val packageName = call.argument<String>("package_name")
openSettings(packageName)
}

"toast" -> {
val message = call.argument("message") ?: ""
val short = call.argument("short_length") ?: true
val message = call.argument<String>("message") ?: ""
val short = call.argument<Boolean>("short_length") ?: true
toast(message, short)
}

"getAppInfo" -> {
val packageName: String = call.argument("package_name") ?: ""
result.success(getAppInfo(getPackageManager(context!!), packageName))
val packageName = call.argument<String>("package_name") ?: ""
val platformTypeName = call.argument<String>("platform_type") ?: ""
val platformType = PlatformType.fromString(platformTypeName)
result.success(getAppInfo(getPackageManager(context!!), packageName, platformType))
}

"isSystemApp" -> {
val packageName: String = call.argument("package_name") ?: ""
val packageName = call.argument<String>("package_name") ?: ""
result.success(isSystemApp(getPackageManager(context!!), packageName))
}

"uninstallApp" -> {
val packageName: String = call.argument("package_name") ?: ""
val packageName = call.argument<String>("package_name") ?: ""
result.success(uninstallApp(packageName))
}

"isAppInstalled" -> {
val packageName: String = call.argument("package_name") ?: ""
val packageName = call.argument<String>("package_name") ?: ""
result.success(isAppInstalled(packageName))
}

Expand All @@ -120,21 +112,25 @@ class InstalledAppsPlugin() : MethodCallHandler, FlutterPlugin, ActivityAware {

private fun getInstalledApps(
excludeSystemApps: Boolean,
excludeUnlaunchable: Boolean,
withIcon: Boolean,
packageNamePrefix: String
packageNamePrefix: String,
platformType: PlatformType?
): List<Map<String, Any?>> {
val packageManager = getPackageManager(context!!)
var installedApps = packageManager.getInstalledApplications(0)
if (excludeSystemApps)
installedApps =
installedApps.filter { app -> !isSystemApp(packageManager, app.packageName) }
if (excludeUnlaunchable)
installedApps = installedApps.filter { app -> isLaunchable(packageManager, app.packageName) }
if (packageNamePrefix.isNotEmpty())
installedApps = installedApps.filter { app ->
app.packageName.startsWith(
packageNamePrefix.lowercase(ENGLISH)
)
}
return installedApps.map { app -> convertAppToMap(packageManager, app, withIcon) }
return installedApps.map { app -> convertAppToMap(packageManager, app, withIcon, platformType) }
}

private fun startApp(packageName: String?): Boolean {
Expand Down Expand Up @@ -163,6 +159,10 @@ class InstalledAppsPlugin() : MethodCallHandler, FlutterPlugin, ActivityAware {
}
}

private fun isLaunchable(packageManager: PackageManager, packageName: String): Boolean {
return packageManager.getLaunchIntentForPackage(packageName) != null
}

private fun openSettings(packageName: String?) {
if (!isAppInstalled(packageName)) {
print("App $packageName is not installed on this device.")
Expand All @@ -178,12 +178,13 @@ class InstalledAppsPlugin() : MethodCallHandler, FlutterPlugin, ActivityAware {

private fun getAppInfo(
packageManager: PackageManager,
packageName: String
packageName: String,
platformType: PlatformType?
): Map<String, Any?>? {
var installedApps = packageManager.getInstalledApplications(0)
installedApps = installedApps.filter { app -> app.packageName == packageName }
return if (installedApps.isEmpty()) null
else convertAppToMap(packageManager, installedApps[0], true)
else convertAppToMap(packageManager, installedApps[0], true, platformType)
}

private fun uninstallApp(packageName: String): Boolean {
Expand All @@ -197,7 +198,6 @@ class InstalledAppsPlugin() : MethodCallHandler, FlutterPlugin, ActivityAware {
}
}


private fun isAppInstalled(packageName: String?): Boolean {
val packageManager: PackageManager = context!!.packageManager
return try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.sharmadhiraj.installed_apps
enum class PlatformType(val value: String) {
FLUTTER("flutter"),
REACT_NATIVE("react_native"),
XAMARIN("xamarin"),
IONIC("ionic"),
NATIVE_OR_OTHERS("native_or_others");

companion object {
fun fromString(platform: String): PlatformType? {
if (platform.isEmpty()) return null;
return when (platform.lowercase()) {
"flutter" -> FLUTTER
"react_native" -> REACT_NATIVE
"xamarin" -> XAMARIN
"ionic" -> IONIC
else -> NATIVE_OR_OTHERS
}
}
}
}
10 changes: 3 additions & 7 deletions android/src/main/kotlin/com/sharmadhiraj/installed_apps/Util.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,12 @@ import android.os.Build.VERSION_CODES.P
import java.io.File

class Util {

companion object {

fun convertAppToMap(
packageManager: PackageManager,
app: ApplicationInfo,
withIcon: Boolean
withIcon: Boolean,
platformType: PlatformType?,
): HashMap<String, Any?> {
val map = HashMap<String, Any?>()
map["name"] = packageManager.getApplicationLabel(app)
Expand All @@ -26,12 +25,11 @@ class Util {
val packageInfo = packageManager.getPackageInfo(app.packageName, 0)
map["version_name"] = packageInfo.versionName
map["version_code"] = getVersionCode(packageInfo)
map["built_with"] = BuiltWithUtil.getPlatform(packageInfo.applicationInfo)
map["built_with"] = platformType?.value ?: BuiltWithUtil.getPlatform(packageInfo.applicationInfo)
map["installed_timestamp"] = File(packageInfo.applicationInfo.sourceDir).lastModified()
return map
}


fun getPackageManager(context: Context): PackageManager {
return context.packageManager
}
Expand All @@ -41,7 +39,5 @@ class Util {
return if (SDK_INT < P) packageInfo.versionCode.toLong()
else packageInfo.longVersionCode
}

}

}
4 changes: 2 additions & 2 deletions example/ios/Flutter/flutter_export_environment.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/sh
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=/Users/dhirajsharma/Drive/SDKs/flutter"
export "FLUTTER_APPLICATION_PATH=/Users/dhirajsharma/Drive/Desk/Personal/Projects/Flutter/installed_apps/example"
export "FLUTTER_ROOT=/Users/velibacik/development/flutter"
export "FLUTTER_APPLICATION_PATH=/Users/velibacik/Desktop/development/package/installed_apps/example"
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
export "FLUTTER_TARGET=lib/main.dart"
export "FLUTTER_BUILD_DIR=build"
Expand Down
8 changes: 5 additions & 3 deletions example/lib/screens/app_info.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:installed_apps/app_info.dart';
import 'package:installed_apps/installed_apps.dart';
import 'package:installed_apps/index.dart';

class AppInfoScreen extends StatelessWidget {
final AppInfo? app;
Expand All @@ -21,7 +20,10 @@ class AppInfoScreen extends StatelessWidget {

Widget _buildAppInfoWithPackageName() {
return FutureBuilder<AppInfo?>(
future: InstalledApps.getAppInfo("com.google.android.gm"),
future: InstalledApps.getAppInfo(
"com.google.android.gm",
BuiltWith.flutter,
),
builder: (BuildContext buildContext, AsyncSnapshot<AppInfo?> snapshot) {
return snapshot.connectionState == ConnectionState.done
? snapshot.hasData && snapshot.data != null
Expand Down
2 changes: 1 addition & 1 deletion example/lib/screens/app_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class AppListScreen extends StatelessWidget {

Widget _buildBody() {
return FutureBuilder<List<AppInfo>>(
future: InstalledApps.getInstalledApps(true, true),
future: InstalledApps.getInstalledApps(false, true, true),
builder: (
BuildContext buildContext,
AsyncSnapshot<List<AppInfo>> snapshot,
Expand Down
2 changes: 1 addition & 1 deletion example/lib/screens/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class HomeScreen extends StatelessWidget {
_buildListItem(
context,
"Installed Apps",
"Get installed apps on device. With options to exclude system app, get app icon & matching package name prefix.",
"Get installed apps on device. With options to exclude system app, exclude unlaunchable apps, get app icon & matching package name prefix.",
() => Navigator.push(
context,
MaterialPageRoute(builder: (context) => AppListScreen()),
Expand Down
Loading