native_bridge.dart 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  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. Future<void> setKeepScreenOn(bool enabled);
  30. }
  31. class NativeBridge implements LauncherPlatform {
  32. static const _appsChannel = MethodChannel('com.wasp.cardash_launcher/apps');
  33. static const _systemChannel = MethodChannel(
  34. 'com.wasp.cardash_launcher/system',
  35. );
  36. static const _mediaChannel = MethodChannel('com.wasp.cardash_launcher/media');
  37. @override
  38. Future<List<AppEntry>> listApps() async {
  39. try {
  40. final raw = await _appsChannel.invokeMethod<dynamic>('listApps');
  41. if (raw is Map && raw['error'] != null) {
  42. throw LauncherPlatformException(raw['error'].toString());
  43. }
  44. if (raw is! List) return const [];
  45. return raw
  46. .whereType<Map>()
  47. .map(AppEntry.fromMap)
  48. .where(
  49. (app) => app.packageName.isNotEmpty && app.activityName.isNotEmpty,
  50. )
  51. .toList(growable: false);
  52. } on PlatformException catch (error) {
  53. throw LauncherPlatformException(
  54. error.message ?? 'Could not load installed apps.',
  55. );
  56. } on MissingPluginException {
  57. throw LauncherPlatformException('The Android app bridge is unavailable.');
  58. }
  59. }
  60. @override
  61. Future<void> launchApp({
  62. required String packageName,
  63. required String activityName,
  64. }) async {
  65. try {
  66. await _appsChannel.invokeMethod<dynamic>('launchApp', {
  67. 'package': packageName,
  68. 'activity': activityName,
  69. });
  70. } on PlatformException catch (error) {
  71. throw LauncherPlatformException(
  72. error.message ?? 'Could not open the app.',
  73. );
  74. } on MissingPluginException {
  75. throw LauncherPlatformException('The Android app bridge is unavailable.');
  76. }
  77. }
  78. @override
  79. Future<bool> isDefaultHome() async {
  80. try {
  81. return await _appsChannel.invokeMethod<bool>('isDefaultHome') ?? false;
  82. } on PlatformException {
  83. return false;
  84. } on MissingPluginException {
  85. return false;
  86. }
  87. }
  88. @override
  89. Future<bool> requestDefaultHome() async {
  90. try {
  91. return await _appsChannel.invokeMethod<bool>('requestDefaultHome') ??
  92. false;
  93. } on PlatformException {
  94. return false;
  95. } on MissingPluginException {
  96. return false;
  97. }
  98. }
  99. @override
  100. Future<SystemStatus> getStatus() async {
  101. try {
  102. final raw = await _systemChannel.invokeMethod<dynamic>('getStatus');
  103. return raw is Map ? SystemStatus.fromMap(raw) : const SystemStatus();
  104. } on PlatformException {
  105. return const SystemStatus();
  106. } on MissingPluginException {
  107. return const SystemStatus();
  108. }
  109. }
  110. @override
  111. Future<VolumeInfo> getVolume() async {
  112. try {
  113. final raw = await _systemChannel.invokeMethod<dynamic>('getVolume');
  114. return raw is Map ? VolumeInfo.fromMap(raw) : const VolumeInfo();
  115. } on PlatformException {
  116. return const VolumeInfo();
  117. } on MissingPluginException {
  118. return const VolumeInfo();
  119. }
  120. }
  121. @override
  122. Future<bool> isMusicPlaying() async {
  123. try {
  124. return await _systemChannel.invokeMethod<bool>('isMusicPlaying') ?? false;
  125. } on PlatformException {
  126. return false;
  127. } on MissingPluginException {
  128. return false;
  129. }
  130. }
  131. @override
  132. Future<MediaPlayback> getMediaState() async {
  133. try {
  134. final raw = await _mediaChannel.invokeMethod<dynamic>('getMediaState');
  135. return raw is Map ? MediaPlayback.fromMap(raw) : const MediaPlayback();
  136. } on PlatformException {
  137. return const MediaPlayback();
  138. } on MissingPluginException {
  139. return const MediaPlayback();
  140. }
  141. }
  142. @override
  143. Future<bool> openNotificationAccessSettings() async {
  144. try {
  145. return await _mediaChannel.invokeMethod<bool>(
  146. 'openNotificationAccessSettings',
  147. ) ??
  148. false;
  149. } on PlatformException {
  150. return false;
  151. } on MissingPluginException {
  152. return false;
  153. }
  154. }
  155. @override
  156. Future<bool> sendMediaKey(String action) async {
  157. try {
  158. return await _systemChannel.invokeMethod<bool>('mediaKey', {
  159. 'action': action,
  160. }) ??
  161. false;
  162. } on PlatformException {
  163. return false;
  164. } on MissingPluginException {
  165. return false;
  166. }
  167. }
  168. @override
  169. Future<void> setKeepScreenOn(bool enabled) async {
  170. try {
  171. await _systemChannel.invokeMethod<dynamic>('setKeepScreenOn', {
  172. 'enabled': enabled,
  173. });
  174. } on PlatformException {
  175. // Older builds simply keep the window behaviour unchanged.
  176. } on MissingPluginException {
  177. // Nothing to apply outside Android.
  178. }
  179. }
  180. @override
  181. Future<void> setVolume(int index) async {
  182. try {
  183. await _systemChannel.invokeMethod<dynamic>('setVolume', {'index': index});
  184. } on PlatformException {
  185. // Volume can be unavailable on some emulators; the UI keeps its local
  186. // value and will reconcile on the next poll.
  187. } on MissingPluginException {
  188. // Nothing to synchronise when running outside Android.
  189. }
  190. }
  191. }