telemetry_bridge.dart 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. import 'dart:async';
  2. import 'package:flutter/services.dart';
  3. import '../models/bluetooth_device_info.dart';
  4. import '../models/bluetooth_power_state.dart';
  5. import '../models/gps_speed.dart';
  6. import '../models/location_reading.dart';
  7. import '../models/obd_state.dart';
  8. class TelemetryException implements Exception {
  9. TelemetryException(this.message, {this.code});
  10. final String message;
  11. /// Stable native error code, when one was supplied.
  12. final String? code;
  13. @override
  14. String toString() => message;
  15. }
  16. /// Bluetooth OBD and location boundary. Kept separate from LauncherPlatform so
  17. /// package/launcher fakes do not need telemetry methods.
  18. abstract class TelemetryPlatform {
  19. Future<List<BluetoothDeviceInfo>> listBondedDevices();
  20. Future<BluetoothPowerState> bluetoothState();
  21. Future<bool> openBluetoothSettings();
  22. Future<bool> ensureBluetoothPermission();
  23. Future<bool> ensureLocationPermission();
  24. Future<LocationReading> getLocation();
  25. Future<void> connectObd(String address);
  26. Future<void> disconnectObd();
  27. Future<ObdState> getObdState();
  28. /// Starts GPS speed updates only when precise location is already allowed.
  29. Future<bool> startGpsSpeed();
  30. Future<void> stopGpsSpeed();
  31. Future<GpsSpeed> getGpsSpeed();
  32. }
  33. class NativeTelemetryBridge implements TelemetryPlatform {
  34. static const _channel = MethodChannel('com.wasp.cardash_launcher/telemetry');
  35. static const _listTimeout = Duration(seconds: 6);
  36. static const _stateTimeout = Duration(seconds: 3);
  37. static const _permissionTimeout = Duration(seconds: 30);
  38. static const _connectTimeout = Duration(seconds: 5);
  39. static const _disconnectTimeout = Duration(seconds: 3);
  40. static const _obdStateTimeout = Duration(seconds: 2);
  41. static const _settingsTimeout = Duration(seconds: 3);
  42. @override
  43. Future<List<BluetoothDeviceInfo>> listBondedDevices() async {
  44. try {
  45. final raw = await _channel
  46. .invokeMethod<dynamic>('listBondedDevices')
  47. .timeout(_listTimeout);
  48. if (raw is! List) return const [];
  49. return raw
  50. .whereType<Map>()
  51. .map(BluetoothDeviceInfo.fromMap)
  52. .where((device) => device.address.isNotEmpty)
  53. .toList(growable: false);
  54. } on TimeoutException {
  55. throw TelemetryException('Bluetooth is not responding.', code: 'timeout');
  56. } on PlatformException catch (error) {
  57. throw TelemetryException(
  58. error.message ?? 'Could not list Bluetooth devices.',
  59. code: error.code,
  60. );
  61. } on MissingPluginException {
  62. throw TelemetryException('The telemetry bridge is unavailable.');
  63. }
  64. }
  65. @override
  66. Future<BluetoothPowerState> bluetoothState() async {
  67. try {
  68. final raw = await _channel
  69. .invokeMethod<String>('bluetoothState')
  70. .timeout(_stateTimeout);
  71. return BluetoothPowerState.fromWire(raw);
  72. } on TimeoutException {
  73. return BluetoothPowerState.unknown;
  74. } on PlatformException {
  75. return BluetoothPowerState.unavailable;
  76. } on MissingPluginException {
  77. return BluetoothPowerState.unavailable;
  78. }
  79. }
  80. @override
  81. Future<bool> openBluetoothSettings() async {
  82. try {
  83. return await _channel
  84. .invokeMethod<bool>('openBluetoothSettings')
  85. .timeout(_settingsTimeout) ??
  86. false;
  87. } on TimeoutException {
  88. return false;
  89. } on PlatformException {
  90. return false;
  91. } on MissingPluginException {
  92. return false;
  93. }
  94. }
  95. @override
  96. Future<bool> ensureBluetoothPermission() async {
  97. try {
  98. return await _channel
  99. .invokeMethod<bool>('ensureBluetoothPermission')
  100. .timeout(_permissionTimeout) ??
  101. false;
  102. } on TimeoutException {
  103. throw TelemetryException(
  104. 'Bluetooth permission request timed out.',
  105. code: 'timeout',
  106. );
  107. } on PlatformException {
  108. return false;
  109. } on MissingPluginException {
  110. return false;
  111. }
  112. }
  113. @override
  114. Future<bool> ensureLocationPermission() async {
  115. try {
  116. return await _channel
  117. .invokeMethod<bool>('ensureLocationPermission')
  118. .timeout(_permissionTimeout) ??
  119. false;
  120. } on TimeoutException {
  121. return false;
  122. } on PlatformException {
  123. return false;
  124. } on MissingPluginException {
  125. return false;
  126. }
  127. }
  128. @override
  129. Future<LocationReading> getLocation() async {
  130. try {
  131. final raw = await _channel
  132. .invokeMethod<dynamic>('getLocation')
  133. .timeout(const Duration(seconds: 20));
  134. if (raw is! Map) {
  135. throw TelemetryException('Could not read the current location.');
  136. }
  137. return LocationReading.fromMap(raw);
  138. } on TimeoutException {
  139. throw TelemetryException('Could not read the current location.');
  140. } on PlatformException catch (error) {
  141. throw TelemetryException(
  142. error.message ?? 'Could not read location.',
  143. code: error.code,
  144. );
  145. } on MissingPluginException {
  146. throw TelemetryException('The telemetry bridge is unavailable.');
  147. }
  148. }
  149. @override
  150. Future<void> connectObd(String address) async {
  151. try {
  152. await _channel
  153. .invokeMethod<dynamic>('connectObd', {'address': address})
  154. .timeout(_connectTimeout);
  155. } on TimeoutException {
  156. throw TelemetryException('Bluetooth is not responding.', code: 'timeout');
  157. } on PlatformException catch (error) {
  158. throw TelemetryException(
  159. error.message ?? 'Could not connect to OBD.',
  160. code: error.code,
  161. );
  162. } on MissingPluginException {
  163. throw TelemetryException('The telemetry bridge is unavailable.');
  164. }
  165. }
  166. @override
  167. Future<void> disconnectObd() async {
  168. try {
  169. await _channel
  170. .invokeMethod<dynamic>('disconnectObd')
  171. .timeout(_disconnectTimeout);
  172. } on TimeoutException {
  173. // The native stop is best effort; never hold up the UI for it.
  174. } on PlatformException {
  175. // Nothing useful to do when already disconnected.
  176. } on MissingPluginException {
  177. // No-op outside Android.
  178. }
  179. }
  180. @override
  181. Future<bool> startGpsSpeed() async {
  182. try {
  183. return await _channel
  184. .invokeMethod<bool>('startGpsSpeed')
  185. .timeout(const Duration(seconds: 3)) ??
  186. false;
  187. } on TimeoutException {
  188. return false;
  189. } on PlatformException {
  190. return false;
  191. } on MissingPluginException {
  192. return false;
  193. }
  194. }
  195. @override
  196. Future<void> stopGpsSpeed() async {
  197. try {
  198. await _channel
  199. .invokeMethod<dynamic>('stopGpsSpeed')
  200. .timeout(const Duration(seconds: 2));
  201. } on TimeoutException {
  202. // Best effort.
  203. } on PlatformException {
  204. // Nothing useful to do when already stopped.
  205. } on MissingPluginException {
  206. // No-op outside Android.
  207. }
  208. }
  209. @override
  210. Future<GpsSpeed> getGpsSpeed() async {
  211. try {
  212. final raw = await _channel
  213. .invokeMethod<dynamic>('getGpsSpeed')
  214. .timeout(const Duration(seconds: 2));
  215. return raw is Map ? GpsSpeed.fromMap(raw) : const GpsSpeed();
  216. } on TimeoutException {
  217. return const GpsSpeed();
  218. } on PlatformException {
  219. return const GpsSpeed();
  220. } on MissingPluginException {
  221. return const GpsSpeed();
  222. }
  223. }
  224. @override
  225. Future<ObdState> getObdState() async {
  226. try {
  227. final raw = await _channel
  228. .invokeMethod<dynamic>('getObdState')
  229. .timeout(_obdStateTimeout);
  230. return raw is Map ? ObdState.fromMap(raw) : const ObdState();
  231. } on TimeoutException {
  232. throw TelemetryException('OBD state is not responding.', code: 'timeout');
  233. } on PlatformException {
  234. return const ObdState();
  235. } on MissingPluginException {
  236. return const ObdState();
  237. }
  238. }
  239. }