native_bridge.dart 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import 'package:flutter/services.dart';
  2. import '../models/app_entry.dart';
  3. import '../models/system_status.dart';
  4. class LauncherPlatformException implements Exception {
  5. LauncherPlatformException(this.message);
  6. final String message;
  7. @override
  8. String toString() => message;
  9. }
  10. /// Thin, testable boundary around Android's package manager, volume and
  11. /// connectivity APIs.
  12. abstract class LauncherPlatform {
  13. Future<List<AppEntry>> listApps();
  14. Future<void> launchApp({
  15. required String packageName,
  16. required String activityName,
  17. });
  18. Future<bool> isDefaultHome();
  19. Future<bool> requestDefaultHome();
  20. Future<SystemStatus> getStatus();
  21. Future<VolumeInfo> getVolume();
  22. Future<void> setVolume(int index);
  23. Future<bool> isMusicPlaying();
  24. /// Sends a standard media key to the current media session.
  25. Future<bool> sendMediaKey(String action);
  26. }
  27. class NativeBridge implements LauncherPlatform {
  28. static const _appsChannel = MethodChannel('com.wasp.cardash_launcher/apps');
  29. static const _systemChannel = MethodChannel(
  30. 'com.wasp.cardash_launcher/system',
  31. );
  32. @override
  33. Future<List<AppEntry>> listApps() async {
  34. try {
  35. final raw = await _appsChannel.invokeMethod<dynamic>('listApps');
  36. if (raw is Map && raw['error'] != null) {
  37. throw LauncherPlatformException(raw['error'].toString());
  38. }
  39. if (raw is! List) return const [];
  40. return raw
  41. .whereType<Map>()
  42. .map(AppEntry.fromMap)
  43. .where(
  44. (app) => app.packageName.isNotEmpty && app.activityName.isNotEmpty,
  45. )
  46. .toList(growable: false);
  47. } on PlatformException catch (error) {
  48. throw LauncherPlatformException(
  49. error.message ?? 'Could not load installed apps.',
  50. );
  51. } on MissingPluginException {
  52. throw LauncherPlatformException('The Android app bridge is unavailable.');
  53. }
  54. }
  55. @override
  56. Future<void> launchApp({
  57. required String packageName,
  58. required String activityName,
  59. }) async {
  60. try {
  61. await _appsChannel.invokeMethod<dynamic>('launchApp', {
  62. 'package': packageName,
  63. 'activity': activityName,
  64. });
  65. } on PlatformException catch (error) {
  66. throw LauncherPlatformException(
  67. error.message ?? 'Could not open the app.',
  68. );
  69. } on MissingPluginException {
  70. throw LauncherPlatformException('The Android app bridge is unavailable.');
  71. }
  72. }
  73. @override
  74. Future<bool> isDefaultHome() async {
  75. try {
  76. return await _appsChannel.invokeMethod<bool>('isDefaultHome') ?? false;
  77. } on PlatformException {
  78. return false;
  79. } on MissingPluginException {
  80. return false;
  81. }
  82. }
  83. @override
  84. Future<bool> requestDefaultHome() async {
  85. try {
  86. return await _appsChannel.invokeMethod<bool>('requestDefaultHome') ??
  87. false;
  88. } on PlatformException {
  89. return false;
  90. } on MissingPluginException {
  91. return false;
  92. }
  93. }
  94. @override
  95. Future<SystemStatus> getStatus() async {
  96. try {
  97. final raw = await _systemChannel.invokeMethod<dynamic>('getStatus');
  98. return raw is Map ? SystemStatus.fromMap(raw) : const SystemStatus();
  99. } on PlatformException {
  100. return const SystemStatus();
  101. } on MissingPluginException {
  102. return const SystemStatus();
  103. }
  104. }
  105. @override
  106. Future<VolumeInfo> getVolume() async {
  107. try {
  108. final raw = await _systemChannel.invokeMethod<dynamic>('getVolume');
  109. return raw is Map ? VolumeInfo.fromMap(raw) : const VolumeInfo();
  110. } on PlatformException {
  111. return const VolumeInfo();
  112. } on MissingPluginException {
  113. return const VolumeInfo();
  114. }
  115. }
  116. @override
  117. Future<bool> isMusicPlaying() async {
  118. try {
  119. return await _systemChannel.invokeMethod<bool>('isMusicPlaying') ?? false;
  120. } on PlatformException {
  121. return false;
  122. } on MissingPluginException {
  123. return false;
  124. }
  125. }
  126. @override
  127. Future<bool> sendMediaKey(String action) async {
  128. try {
  129. return await _systemChannel.invokeMethod<bool>('mediaKey', {
  130. 'action': action,
  131. }) ??
  132. false;
  133. } on PlatformException {
  134. return false;
  135. } on MissingPluginException {
  136. return false;
  137. }
  138. }
  139. @override
  140. Future<void> setVolume(int index) async {
  141. try {
  142. await _systemChannel.invokeMethod<dynamic>('setVolume', {'index': index});
  143. } on PlatformException {
  144. // Volume can be unavailable on some emulators; the UI keeps its local
  145. // value and will reconcile on the next poll.
  146. } on MissingPluginException {
  147. // Nothing to synchronise when running outside Android.
  148. }
  149. }
  150. }