| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- import 'dart:async';
- import 'package:flutter/services.dart';
- import '../models/bluetooth_device_info.dart';
- import '../models/bluetooth_power_state.dart';
- import '../models/gps_speed.dart';
- import '../models/location_reading.dart';
- import '../models/obd_state.dart';
- class TelemetryException implements Exception {
- TelemetryException(this.message, {this.code});
- final String message;
- /// Stable native error code, when one was supplied.
- final String? code;
- @override
- String toString() => message;
- }
- /// Bluetooth OBD and location boundary. Kept separate from LauncherPlatform so
- /// package/launcher fakes do not need telemetry methods.
- abstract class TelemetryPlatform {
- Future<List<BluetoothDeviceInfo>> listBondedDevices();
- Future<BluetoothPowerState> bluetoothState();
- Future<bool> openBluetoothSettings();
- Future<bool> ensureBluetoothPermission();
- Future<bool> ensureLocationPermission();
- Future<LocationReading> getLocation();
- Future<void> connectObd(String address);
- Future<void> disconnectObd();
- Future<ObdState> getObdState();
- /// Starts GPS speed updates only when precise location is already allowed.
- Future<bool> startGpsSpeed();
- Future<void> stopGpsSpeed();
- Future<GpsSpeed> getGpsSpeed();
- }
- class NativeTelemetryBridge implements TelemetryPlatform {
- static const _channel = MethodChannel('com.wasp.cardash_launcher/telemetry');
- static const _listTimeout = Duration(seconds: 6);
- static const _stateTimeout = Duration(seconds: 3);
- static const _permissionTimeout = Duration(seconds: 30);
- static const _connectTimeout = Duration(seconds: 5);
- static const _disconnectTimeout = Duration(seconds: 3);
- static const _obdStateTimeout = Duration(seconds: 2);
- static const _settingsTimeout = Duration(seconds: 3);
- @override
- Future<List<BluetoothDeviceInfo>> listBondedDevices() async {
- try {
- final raw = await _channel
- .invokeMethod<dynamic>('listBondedDevices')
- .timeout(_listTimeout);
- if (raw is! List) return const [];
- return raw
- .whereType<Map>()
- .map(BluetoothDeviceInfo.fromMap)
- .where((device) => device.address.isNotEmpty)
- .toList(growable: false);
- } on TimeoutException {
- throw TelemetryException('Bluetooth is not responding.', code: 'timeout');
- } on PlatformException catch (error) {
- throw TelemetryException(
- error.message ?? 'Could not list Bluetooth devices.',
- code: error.code,
- );
- } on MissingPluginException {
- throw TelemetryException('The telemetry bridge is unavailable.');
- }
- }
- @override
- Future<BluetoothPowerState> bluetoothState() async {
- try {
- final raw = await _channel
- .invokeMethod<String>('bluetoothState')
- .timeout(_stateTimeout);
- return BluetoothPowerState.fromWire(raw);
- } on TimeoutException {
- return BluetoothPowerState.unknown;
- } on PlatformException {
- return BluetoothPowerState.unavailable;
- } on MissingPluginException {
- return BluetoothPowerState.unavailable;
- }
- }
- @override
- Future<bool> openBluetoothSettings() async {
- try {
- return await _channel
- .invokeMethod<bool>('openBluetoothSettings')
- .timeout(_settingsTimeout) ??
- false;
- } on TimeoutException {
- return false;
- } on PlatformException {
- return false;
- } on MissingPluginException {
- return false;
- }
- }
- @override
- Future<bool> ensureBluetoothPermission() async {
- try {
- return await _channel
- .invokeMethod<bool>('ensureBluetoothPermission')
- .timeout(_permissionTimeout) ??
- false;
- } on TimeoutException {
- throw TelemetryException(
- 'Bluetooth permission request timed out.',
- code: 'timeout',
- );
- } on PlatformException {
- return false;
- } on MissingPluginException {
- return false;
- }
- }
- @override
- Future<bool> ensureLocationPermission() async {
- try {
- return await _channel
- .invokeMethod<bool>('ensureLocationPermission')
- .timeout(_permissionTimeout) ??
- false;
- } on TimeoutException {
- return false;
- } on PlatformException {
- return false;
- } on MissingPluginException {
- return false;
- }
- }
- @override
- Future<LocationReading> getLocation() async {
- try {
- final raw = await _channel
- .invokeMethod<dynamic>('getLocation')
- .timeout(const Duration(seconds: 20));
- if (raw is! Map) {
- throw TelemetryException('Could not read the current location.');
- }
- return LocationReading.fromMap(raw);
- } on TimeoutException {
- throw TelemetryException('Could not read the current location.');
- } on PlatformException catch (error) {
- throw TelemetryException(
- error.message ?? 'Could not read location.',
- code: error.code,
- );
- } on MissingPluginException {
- throw TelemetryException('The telemetry bridge is unavailable.');
- }
- }
- @override
- Future<void> connectObd(String address) async {
- try {
- await _channel
- .invokeMethod<dynamic>('connectObd', {'address': address})
- .timeout(_connectTimeout);
- } on TimeoutException {
- throw TelemetryException('Bluetooth is not responding.', code: 'timeout');
- } on PlatformException catch (error) {
- throw TelemetryException(
- error.message ?? 'Could not connect to OBD.',
- code: error.code,
- );
- } on MissingPluginException {
- throw TelemetryException('The telemetry bridge is unavailable.');
- }
- }
- @override
- Future<void> disconnectObd() async {
- try {
- await _channel
- .invokeMethod<dynamic>('disconnectObd')
- .timeout(_disconnectTimeout);
- } on TimeoutException {
- // The native stop is best effort; never hold up the UI for it.
- } on PlatformException {
- // Nothing useful to do when already disconnected.
- } on MissingPluginException {
- // No-op outside Android.
- }
- }
- @override
- Future<bool> startGpsSpeed() async {
- try {
- return await _channel
- .invokeMethod<bool>('startGpsSpeed')
- .timeout(const Duration(seconds: 3)) ??
- false;
- } on TimeoutException {
- return false;
- } on PlatformException {
- return false;
- } on MissingPluginException {
- return false;
- }
- }
- @override
- Future<void> stopGpsSpeed() async {
- try {
- await _channel
- .invokeMethod<dynamic>('stopGpsSpeed')
- .timeout(const Duration(seconds: 2));
- } on TimeoutException {
- // Best effort.
- } on PlatformException {
- // Nothing useful to do when already stopped.
- } on MissingPluginException {
- // No-op outside Android.
- }
- }
- @override
- Future<GpsSpeed> getGpsSpeed() async {
- try {
- final raw = await _channel
- .invokeMethod<dynamic>('getGpsSpeed')
- .timeout(const Duration(seconds: 2));
- return raw is Map ? GpsSpeed.fromMap(raw) : const GpsSpeed();
- } on TimeoutException {
- return const GpsSpeed();
- } on PlatformException {
- return const GpsSpeed();
- } on MissingPluginException {
- return const GpsSpeed();
- }
- }
- @override
- Future<ObdState> getObdState() async {
- try {
- final raw = await _channel
- .invokeMethod<dynamic>('getObdState')
- .timeout(_obdStateTimeout);
- return raw is Map ? ObdState.fromMap(raw) : const ObdState();
- } on TimeoutException {
- throw TelemetryException('OBD state is not responding.', code: 'timeout');
- } on PlatformException {
- return const ObdState();
- } on MissingPluginException {
- return const ObdState();
- }
- }
- }
|