Skip to content

Commit

Permalink
refactor: improve application icon loading
Browse files Browse the repository at this point in the history
  • Loading branch information
JunioJsv committed Jul 30, 2024
1 parent 0b3ef5c commit 456c4fe
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,31 @@ package juniojsv.minimum
import android.app.Activity
import android.content.Intent
import android.content.Intent.ACTION_DELETE
import android.content.IntentFilter
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Canvas
import android.net.Uri
import android.os.Build
import android.provider.Settings
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import java.io.ByteArrayOutputStream

class ApplicationsManagerPlugin : FlutterPlugin, ActivityAware {
import juniojsv.minimum.utils.toByteArray
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext

class ApplicationsManagerPlugin : FlutterPlugin, ActivityAware, CoroutineScope {
private lateinit var channel: MethodChannel
private lateinit var pm: PackageManager
private lateinit var activity: Activity
override val coroutineContext: CoroutineContext
get() = Dispatchers.Default + Job()

companion object {
const val CHANNEL_NAME = "juniojsv.minimum/applications_manager_plugin"
Expand Down Expand Up @@ -78,26 +83,27 @@ class ApplicationsManagerPlugin : FlutterPlugin, ActivityAware {
}
}

private fun getInstalledApplications(result: MethodChannel.Result) {
private fun getInstalledApplications(result: MethodChannel.Result) = launch {
val flags = PackageManager.GET_META_DATA
val apps = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val infos = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
pm.getInstalledApplications(PackageManager.ApplicationInfoFlags.of(flags.toLong()))
} else {
pm.getInstalledApplications(flags)
}

val json = mutableListOf<Map<String, Any>>()

for (app in apps) {
val packageName = app.packageName
val isMinimumAppPackage = packageName == BuildConfig.APPLICATION_ID
val isLaunchable = pm.getLaunchIntentForPackage(packageName) != null
if (!isMinimumAppPackage && isLaunchable) {
json.add(getApplicationJson(app, flags))
val applications = infos.map { app ->
async {
val packageName = app.packageName
val isMinimumAppPackage = packageName == BuildConfig.APPLICATION_ID
val isLaunchable = pm.getLaunchIntentForPackage(packageName) != null
if (!isMinimumAppPackage && isLaunchable) {
return@async getApplicationJson(app, flags)
}
return@async null
}
}
}.awaitAll().filterNotNull()

result.success(json)
result.success(applications)
}

private fun getApplicationJson(app: ApplicationInfo, flags: Int): Map<String, Any> {
Expand All @@ -123,8 +129,8 @@ class ApplicationsManagerPlugin : FlutterPlugin, ActivityAware {
result: MethodChannel.Result
) {
val flags = PackageManager.GET_META_DATA
val packageName = getPackageNameFromMethodCall(call, result) ?: return
try {
val packageName = call.argument<String>("package_name")!!
val app = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
pm.getApplicationInfo(
packageName,
Expand All @@ -139,21 +145,9 @@ class ApplicationsManagerPlugin : FlutterPlugin, ActivityAware {
}
}

private fun getPackageNameFromMethodCall(
call: MethodCall,
result: MethodChannel.Result
): String? {
val packageName = call.argument<String>("package_name")
if (packageName == null) {
result.error("package_name_is_null", null, null)
}

return packageName;
}

private fun launchApplication(call: MethodCall, result: MethodChannel.Result) {
val packageName = getPackageNameFromMethodCall(call, result) ?: return
try {
val packageName = call.argument<String>("package_name")!!
val intent = pm.getLaunchIntentForPackage(packageName)
if (intent == null) {
result.error("cant_launch_$packageName", null, null)
Expand All @@ -165,31 +159,22 @@ class ApplicationsManagerPlugin : FlutterPlugin, ActivityAware {
}
}

private fun getApplicationIcon(call: MethodCall, result: MethodChannel.Result) {
val packageName = getPackageNameFromMethodCall(call, result) ?: return
private fun getApplicationIcon(call: MethodCall, result: MethodChannel.Result) = launch {
try {
val icon = pm.getApplicationIcon(packageName)
val bitmap = Bitmap.createBitmap(
icon.intrinsicWidth,
icon.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
icon.setBounds(0, 0, canvas.width, canvas.height)
icon.draw(canvas)
val bytes = ByteArrayOutputStream().use {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
it.toByteArray()
}
result.success(bytes)
val packageName = call.argument<String>("package_name")
val size = call.argument<Int>("size")
val icon = if (packageName != null)
pm.getApplicationIcon(packageName)
else activity.getDrawable(android.R.drawable.sym_def_app_icon)!!
result.success(icon.toByteArray(size, size))
} catch (e: Exception) {
result.error(GET_APPLICATION_ICON, e.message, null)
}
}

private fun openApplicationDetails(call: MethodCall, result: MethodChannel.Result) {
val packageName = getPackageNameFromMethodCall(call, result) ?: return
try {
val packageName = call.argument<String>("package_name")!!
activity.startActivity(
Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Expand All @@ -203,8 +188,8 @@ class ApplicationsManagerPlugin : FlutterPlugin, ActivityAware {
}

private fun uninstallApplication(call: MethodCall, result: MethodChannel.Result) {
val packageName = getPackageNameFromMethodCall(call, result) ?: return
try {
val packageName = call.argument<String>("package_name")!!
activity.startActivity(
Intent(
ACTION_DELETE,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package juniojsv.minimum.utils

import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.os.Build
import java.io.ByteArrayOutputStream

private val compressFormat =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
Bitmap.CompressFormat.WEBP_LOSSY
else Bitmap.CompressFormat.PNG

fun Drawable.toByteArray(width: Int? = null, height: Int? = null): ByteArray {
val bitmap = Bitmap.createBitmap(
width ?: intrinsicWidth,
height ?: intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
setBounds(0, 0, canvas.width, canvas.height)
draw(canvas)

return ByteArrayOutputStream().use {
bitmap.compress(compressFormat, 75, it)
bitmap.recycle()
it.toByteArray()
}
}
2 changes: 1 addition & 1 deletion android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true
android.enableJetifier=false
android.enableR8.fullMode=true
11 changes: 6 additions & 5 deletions lib/features/applications/widgets/application_icon.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ class ApplicationIconState extends State<ApplicationIcon>
future: icon,
builder: (context, snapshot) {
final bytes = snapshot.data;
if (bytes == null) return const SizedBox.expand();
return DecoratedBox(
decoration: BoxDecoration(
boxShadow: widget.shadow
Expand All @@ -43,10 +42,12 @@ class ApplicationIconState extends State<ApplicationIcon>
: null,
),
child: SizedBox.expand(
child: FadeInImage(
placeholder: MemoryImage(kTransparentImage),
image: MemoryImage(bytes),
),
child: bytes != null
? FadeInImage(
placeholder: MemoryImage(kTransparentImage),
image: MemoryImage(bytes),
)
: Container(),
),
);
},
Expand Down
12 changes: 9 additions & 3 deletions lib/services/applications_manager_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,19 @@ class ApplicationsManagerService {
);
}

Future<Uint8List> getApplicationIcon(String package) async {
Future<Uint8List> getApplicationIcon([
String? package,
int size = 96,
]) async {
final bytes = await _icons.get(
package,
package ?? 'default',
() async {
final bytes = await channel.invokeMethod<Uint8List>(
kGetApplicationIcon,
{'package_name': package},
{
if (package != null) 'package_name': package,
'size': size,
},
);

return bytes!;
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: minimum
description: "Android app launcher"
publish_to: 'none'

version: 2.0.1+144
version: 2.0.2+145

environment:
sdk: '>=3.4.0 <4.0.0'
Expand Down

0 comments on commit 456c4fe

Please sign in to comment.