diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DebugHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DebugHandler.kt index 29cca22e4..4aebabf55 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DebugHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DebugHandler.kt @@ -4,6 +4,8 @@ import android.content.ContentUris import android.content.Context import android.database.Cursor import android.graphics.BitmapFactory +import android.media.MediaCodecInfo +import android.media.MediaCodecList import android.net.Uri import android.os.Build import android.os.Handler @@ -47,6 +49,7 @@ class DebugHandler(private val context: Context) : MethodCallHandler { "safeExceptionInCoroutine" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result) { _, _ -> throw TestException() } } "getContextDirs" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getContextDirs) } + "getCodecs" -> safe(call, result, ::getCodecs) "getEnv" -> safe(call, result, ::getEnv) "getBitmapFactoryInfo" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getBitmapFactoryInfo) } @@ -83,6 +86,40 @@ class DebugHandler(private val context: Context) : MethodCallHandler { result.success(dirs) } + private fun getCodecs(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) { + val codecs = ArrayList() + + fun getFields(info: MediaCodecInfo): FieldMap { + val fields: FieldMap = hashMapOf( + "name" to info.name, + "isEncoder" to info.isEncoder, + "supportedTypes" to info.supportedTypes.joinToString(", "), + ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + if (info.canonicalName != info.name) fields["canonicalName"] = info.canonicalName + if (info.isAlias) fields["isAlias"] to info.isAlias + if (info.isHardwareAccelerated) fields["isHardwareAccelerated"] to info.isHardwareAccelerated + if (info.isSoftwareOnly) fields["isSoftwareOnly"] to info.isSoftwareOnly + if (info.isVendor) fields["isVendor"] to info.isVendor + } + return fields + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + codecs.addAll(MediaCodecList(MediaCodecList.REGULAR_CODECS).codecInfos.map(::getFields)) + } else { + @Suppress("deprecation") + val count = MediaCodecList.getCodecCount() + for (i in 0 until count) { + @Suppress("deprecation") + val info = MediaCodecList.getCodecInfoAt(i) + codecs.add(getFields(info)) + } + } + + result.success(codecs) + } + private fun getEnv(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) { result.success(System.getenv()) } diff --git a/lib/services/android_debug_service.dart b/lib/services/android_debug_service.dart index 15f63b70d..ee522c45c 100644 --- a/lib/services/android_debug_service.dart +++ b/lib/services/android_debug_service.dart @@ -56,6 +56,16 @@ class AndroidDebugService { return {}; } + static Future> getCodecs() async { + try { + final result = await platform.invokeMethod('getCodecs'); + if (result != null) return (result as List).cast(); + } on PlatformException catch (e, stack) { + await reportService.recordError(e, stack); + } + return []; + } + static Future getEnv() async { try { final result = await platform.invokeMethod('getEnv'); diff --git a/lib/widgets/debug/android_codecs.dart b/lib/widgets/debug/android_codecs.dart new file mode 100644 index 000000000..2823aae0e --- /dev/null +++ b/lib/widgets/debug/android_codecs.dart @@ -0,0 +1,72 @@ +import 'package:aves/services/android_debug_service.dart'; +import 'package:aves/widgets/common/identity/aves_expansion_tile.dart'; +import 'package:aves/widgets/common/identity/highlight_title.dart'; +import 'package:aves/widgets/viewer/info/common.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; + +class DebugAndroidCodecSection extends StatefulWidget { + const DebugAndroidCodecSection({Key? key}) : super(key: key); + + @override + _DebugAndroidCodecSectionState createState() => _DebugAndroidCodecSectionState(); +} + +class _DebugAndroidCodecSectionState extends State with AutomaticKeepAliveClientMixin { + late Future> _loader; + + @override + void initState() { + super.initState(); + _loader = AndroidDebugService.getCodecs(); + } + + @override + Widget build(BuildContext context) { + super.build(context); + + return AvesExpansionTile( + title: 'Android Codecs', + children: [ + Padding( + padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8), + child: FutureBuilder>( + future: _loader, + builder: (context, snapshot) { + if (snapshot.hasError) return Text(snapshot.error.toString()); + if (snapshot.connectionState != ConnectionState.done) return const SizedBox.shrink(); + final codecs = snapshot.data!.map((codec) { + return codec.map((k, v) => MapEntry(k.toString(), v.toString())); + }).toList() + ..sort((a, b) => compareAsciiUpperCase(a['supportedTypes'] ?? '', b['supportedTypes'] ?? '')); + final byEncoder = groupBy, bool>(codecs, (v) => v['isEncoder'] == 'true'); + final decoders = byEncoder[false] ?? []; + final encoders = byEncoder[true] ?? []; + Widget _toCodecColumn(List> codecs) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: codecs + .expand((v) => [ + InfoRowGroup(info: Map.fromEntries(v.entries.where((kv) => kv.key != 'isEncoder'))), + const Divider(), + ]) + .toList(), + ); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const HighlightTitle(title: 'Decoders'), + _toCodecColumn(decoders), + const HighlightTitle(title: 'Encoders'), + _toCodecColumn(encoders), + ], + ); + }, + ), + ), + ], + ); + } + + @override + bool get wantKeepAlive => true; +} diff --git a/lib/widgets/debug/app_debug_page.dart b/lib/widgets/debug/app_debug_page.dart index 742455f33..1b4a3ec08 100644 --- a/lib/widgets/debug/app_debug_page.dart +++ b/lib/widgets/debug/app_debug_page.dart @@ -4,6 +4,7 @@ import 'package:aves/services/analysis_service.dart'; import 'package:aves/widgets/common/identity/aves_expansion_tile.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves/widgets/debug/android_apps.dart'; +import 'package:aves/widgets/debug/android_codecs.dart'; import 'package:aves/widgets/debug/android_dirs.dart'; import 'package:aves/widgets/debug/android_env.dart'; import 'package:aves/widgets/debug/cache.dart'; @@ -46,6 +47,7 @@ class _AppDebugPageState extends State { children: [ _buildGeneralTabView(), const DebugAndroidAppSection(), + const DebugAndroidCodecSection(), const DebugAndroidDirSection(), const DebugAndroidEnvironmentSection(), const DebugCacheSection(),