native_bridge.dart 5.4 KB

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