blob: 39582a08c868b1c353de556c12e1eabd046228be [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {at_commands as at, core::fmt::Debug, fidl_fuchsia_bluetooth_hfp::CallState};
use crate::procedure::AgUpdate;
/// This implementation supports the 7 indicators defined in HFP v1.8 Section 4.35.
/// The indices of these indicators are fixed.
pub(crate) const SERVICE_INDICATOR_INDEX: usize = 1;
pub(crate) const CALL_INDICATOR_INDEX: usize = 2;
pub(crate) const CALL_SETUP_INDICATOR_INDEX: usize = 3;
pub(crate) const CALL_HELD_INDICATOR_INDEX: usize = 4;
pub(crate) const SIGNAL_INDICATOR_INDEX: usize = 5;
pub(crate) const ROAM_INDICATOR_INDEX: usize = 6;
pub(crate) const BATT_CHG_INDICATOR_INDEX: usize = 7;
/// The supported HF indicators.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum HfIndicator {
EnhancedSafety(bool),
BatteryLevel(u8),
}
/// A single indicator status + value.
#[derive(Clone, Copy, Debug)]
struct Indicator<T: Clone + Copy + Debug> {
/// Whether this indicator is enabled or not.
enabled: bool,
/// The value of the indicator.
value: Option<T>,
}
impl<T: Clone + Copy + Debug> Default for Indicator<T> {
fn default() -> Self {
Self { enabled: false, value: None }
}
}
/// The supported HF indicators and their enabled/disabled status & values.
/// Defined in HFP v1.8 Section 4.36.
#[derive(Clone, Copy, Debug, Default)]
pub struct HfIndicators {
/// The Enhanced Safety HF indicator. There are only two potential values (enabled, disabled).
enhanced_safety: Indicator<bool>,
/// The Battery Level HF indicator. Can be any integer value between [0, 100].
battery_level: Indicator<u8>,
}
impl HfIndicators {
/// The Maximum Battery Level value for the `battery_level` indicator.
/// Defined in HFP v1.8 Section 4.35.
const MAX_BATTERY_LEVEL: u8 = 100;
/// Enables the supported HF indicators based on the provided AT `indicators`.
pub fn enable_indicators(&mut self, indicators: Vec<at::BluetoothHFIndicator>) {
for ind in indicators {
if ind == at::BluetoothHFIndicator::EnhancedSafety {
self.enhanced_safety.enabled = true;
}
if ind == at::BluetoothHFIndicator::BatteryLevel {
self.battery_level.enabled = true;
}
}
}
/// Updates the `indicator` with the provided `value`.
/// Returns Error if the indicator is disabled or if the `value` is out of bounds.
/// Returns a valid HfIndicator on success.
pub fn update_indicator_value(
&mut self,
indicator: at::BluetoothHFIndicator,
value: i64,
) -> Result<HfIndicator, ()> {
let ind = match indicator {
at::BluetoothHFIndicator::EnhancedSafety if self.enhanced_safety.enabled => {
if value != 0 && value != 1 {
return Err(());
}
let v = value != 0;
self.enhanced_safety.value = Some(v);
HfIndicator::EnhancedSafety(v)
}
at::BluetoothHFIndicator::BatteryLevel if self.battery_level.enabled => {
if value < 0 || value > Self::MAX_BATTERY_LEVEL.into() {
return Err(());
}
let v = value as u8;
self.battery_level.value = Some(v);
HfIndicator::BatteryLevel(v)
}
ind => {
log::warn!("Received HF indicator update for disabled indicator: {:?}", ind);
return Err(());
}
};
Ok(ind)
}
/// Returns the +BIND response for the current HF indicator status.
pub fn bind_response(&self) -> Vec<at::Response> {
vec![
at::success(at::Success::BindStatus {
anum: at::BluetoothHFIndicator::EnhancedSafety,
state: self.enhanced_safety.enabled,
}),
at::success(at::Success::BindStatus {
anum: at::BluetoothHFIndicator::BatteryLevel,
state: self.battery_level.enabled,
}),
at::Response::Ok,
]
}
}
/// A collection of indicators supported by the AG.
#[derive(Debug, Default, Clone, Copy)]
pub struct AgIndicators {
pub service: bool,
pub call: Call,
pub callsetup: CallSetup,
pub callheld: CallHeld,
pub signal: u8,
pub roam: bool,
pub battchg: u8,
}
/// The supported phone status update indicators.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum AgIndicator {
Service(u8),
Call(u8),
CallSetup(u8),
CallHeld(u8),
Signal(u8),
Roam(u8),
BatteryLevel(u8),
}
impl From<AgIndicator> for at::Response {
fn from(src: AgIndicator) -> at::Response {
match src {
AgIndicator::Service(v) => at::Response::Success(at::Success::Ciev {
ind: SERVICE_INDICATOR_INDEX as i64,
value: v as i64,
}),
AgIndicator::Call(v) => at::Response::Success(at::Success::Ciev {
ind: CALL_INDICATOR_INDEX as i64,
value: v as i64,
}),
AgIndicator::CallSetup(v) => at::Response::Success(at::Success::Ciev {
ind: CALL_SETUP_INDICATOR_INDEX as i64,
value: v as i64,
}),
AgIndicator::CallHeld(v) => at::Response::Success(at::Success::Ciev {
ind: CALL_HELD_INDICATOR_INDEX as i64,
value: v as i64,
}),
AgIndicator::Signal(v) => at::Response::Success(at::Success::Ciev {
ind: SIGNAL_INDICATOR_INDEX as i64,
value: v as i64,
}),
AgIndicator::Roam(v) => at::Response::Success(at::Success::Ciev {
ind: ROAM_INDICATOR_INDEX as i64,
value: v as i64,
}),
AgIndicator::BatteryLevel(v) => at::Response::Success(at::Success::Ciev {
ind: BATT_CHG_INDICATOR_INDEX as i64,
value: v as i64,
}),
}
}
}
impl From<Call> for AgIndicator {
fn from(src: Call) -> Self {
Self::Call(src as u8)
}
}
impl From<CallSetup> for AgIndicator {
fn from(src: CallSetup) -> Self {
Self::CallSetup(src as u8)
}
}
impl From<CallHeld> for AgIndicator {
fn from(src: CallHeld) -> Self {
Self::CallHeld(src as u8)
}
}
impl From<AgIndicator> for AgUpdate {
fn from(src: AgIndicator) -> Self {
Self::PhoneStatusIndicator(src)
}
}
/// The current AG Indicators Reporting status with the active/inactive status of each
/// supported indicator.
///
/// Per HFP v1.8 Section 4.35, the Call, Call Setup, and Call Held indicators
/// must always be activated; these fields are read-only and will always
/// be set and therefore are omitted from the `AgIndicatorsReporting` object.
///
/// It is valid to toggle the activeness of specific event even if Indicators Reporting is
/// disabled.
///
/// By default, all indicators are set to enabled.
#[derive(Clone, Debug, PartialEq)]
pub struct AgIndicatorsReporting {
is_enabled: bool,
/// The event indicators.
service: bool,
signal: bool,
roam: bool,
batt_chg: bool,
}
impl AgIndicatorsReporting {
#[cfg(test)]
pub fn set_signal(&mut self, toggle: bool) {
self.signal = toggle;
}
#[cfg(test)]
pub fn set_service(&mut self, toggle: bool) {
self.service = toggle;
}
#[cfg(test)]
pub fn set_batt_chg(&mut self, toggle: bool) {
self.batt_chg = toggle;
}
#[cfg(test)]
pub fn new_enabled() -> Self {
Self { is_enabled: true, service: true, signal: true, roam: true, batt_chg: true }
}
pub fn new_disabled() -> Self {
Self { is_enabled: false, service: true, signal: true, roam: true, batt_chg: true }
}
/// Sets the indicators reporting state to enabled while maintaining current indicator
/// flags.
pub fn enable(&mut self) {
self.is_enabled = true;
}
/// Sets the indicators reporting state to disabled while maintaining the current
/// indicator flags.
pub fn disable(&mut self) {
self.is_enabled = false;
}
/// Updates the indicators with any indicators specified in `flags`.
pub fn update_from_flags(&mut self, flags: Vec<Option<bool>>) {
for (idx, flag) in flags.into_iter().enumerate() {
// The indicator indices are "1-indexed".
let index = idx + 1;
let toggle = if let Some(b) = flag { b } else { continue };
// See HFP v1.8 Section 4.35 for the flags that cannot be toggled.
// Any indicators beyond the 7 supported in this implementation can be safely ignored.
match index {
SERVICE_INDICATOR_INDEX => self.service = toggle,
CALL_INDICATOR_INDEX | CALL_SETUP_INDICATOR_INDEX | CALL_HELD_INDICATOR_INDEX => (),
SIGNAL_INDICATOR_INDEX => self.signal = toggle,
ROAM_INDICATOR_INDEX => self.roam = toggle,
BATT_CHG_INDICATOR_INDEX => self.batt_chg = toggle,
_ => break,
}
}
}
/// Returns true if indicators reporting is enabled and the provided `status` indicator should
/// be sent to the peer.
pub fn indicator_enabled(&self, status: &AgIndicator) -> bool {
self.is_enabled
&& match status {
AgIndicator::Service(_) => self.service,
AgIndicator::Call(_) | AgIndicator::CallSetup(_) | AgIndicator::CallHeld(_) => true,
AgIndicator::Signal(_) => self.signal,
AgIndicator::Roam(_) => self.roam,
AgIndicator::BatteryLevel(_) => self.batt_chg,
}
}
}
impl Default for AgIndicatorsReporting {
/// The default indicators reporting state is disabled.
/// Per HFP v1.8 Section 4.2.1.3, the HF will _always_ request to enable indicators
/// reporting in the SLCI procedure (See +CMER AT command).
fn default() -> Self {
Self::new_disabled()
}
}
/// The Call Indicator as specified in HFP v1.8, Section 4.10.1
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum Call {
/// There are no calls present in the AG (active or held).
None,
/// There is at least one call present in the AG (active or held).
Some,
}
impl Default for Call {
fn default() -> Self {
Self::None
}
}
impl From<Call> for bool {
fn from(call: Call) -> Self {
match call {
Call::None => false,
Call::Some => true,
}
}
}
impl From<bool> for Call {
fn from(call: bool) -> Self {
match call {
false => Self::None,
true => Self::Some,
}
}
}
impl Call {
/// Find the Call state based on all the calls in `iter`.
pub fn find(mut iter: impl Iterator<Item = CallState>) -> Self {
iter.any(|state| [CallState::OngoingActive, CallState::OngoingHeld].contains(&state)).into()
}
}
/// The Callsetup Indicator as specified in HFP v1.8, Section 4.10.2
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum CallSetup {
/// No call setup in progress.
None = 0,
/// Incoming call setup in progress.
Incoming = 1,
/// Outgoing call setup in dialing state.
OutgoingDialing = 2,
/// Outgoing call setup in alerting state.
OutgoingAlerting = 3,
}
impl Default for CallSetup {
fn default() -> Self {
Self::None
}
}
impl CallSetup {
/// Find CallSetup state based on the first call in `iter` that is in a callsetup state.
pub fn find(mut iter: impl Iterator<Item = CallState>) -> Self {
iter.find(|state| {
[
CallState::IncomingRinging,
CallState::IncomingWaiting,
CallState::OutgoingAlerting,
CallState::OutgoingDialing,
]
.contains(&state)
})
.map(CallSetup::from)
.unwrap_or(CallSetup::None)
}
}
impl From<CallState> for CallSetup {
fn from(state: CallState) -> Self {
match state {
CallState::IncomingRinging | CallState::IncomingWaiting => Self::Incoming,
CallState::OutgoingDialing => Self::OutgoingDialing,
CallState::OutgoingAlerting => Self::OutgoingAlerting,
_ => Self::None,
}
}
}
/// The Callheld Indicator as specified in HFP v1.8, Section 4.10.3
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum CallHeld {
/// No calls held.
None = 0,
/// Call is placed on hold or active/held calls swapped (The AG has both an active AND a held
/// call).
HeldAndActive = 1,
/// Call on hold, no active call.
Held = 2,
}
impl Default for CallHeld {
fn default() -> Self {
Self::None
}
}
impl CallHeld {
/// Find the CallHeld state based on all calls in `iter`.
pub fn find(mut iter: impl Iterator<Item = CallState> + Clone) -> Self {
let any_held = iter.clone().any(|state| state == CallState::OngoingHeld);
let any_active = iter.any(|state| state == CallState::OngoingActive);
match (any_held, any_active) {
(true, false) => CallHeld::Held,
(true, true) => CallHeld::HeldAndActive,
(false, _) => CallHeld::None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct CallIndicators {
pub call: Call,
pub callsetup: CallSetup,
pub callheld: CallHeld,
}
impl CallIndicators {
/// Find CallIndicators based on all items in `iter`.
pub fn find(iter: impl Iterator<Item = CallState> + Clone) -> Self {
let call = Call::find(iter.clone());
let callsetup = CallSetup::find(iter.clone());
let callheld = CallHeld::find(iter);
CallIndicators { call, callsetup, callheld }
}
/// A list of all the statuses that have changed between `other` and self.
/// The values in the list are the values found in `self`.
pub fn difference(&self, other: Self) -> CallIndicatorsUpdates {
let mut changes = CallIndicatorsUpdates::default();
if other.call != self.call {
changes.call = Some(self.call);
}
if other.callsetup != self.callsetup {
changes.callsetup = Some(self.callsetup);
}
if other.callheld != self.callheld {
changes.callheld = Some(self.callheld);
}
changes
}
}
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct CallIndicatorsUpdates {
pub call: Option<Call>,
pub callsetup: Option<CallSetup>,
pub callheld: Option<CallHeld>,
}
impl CallIndicatorsUpdates {
/// Returns true if all fields are `None`
pub fn is_empty(&self) -> bool {
self.call.is_none() && self.callsetup.is_none() && self.callheld.is_none()
}
/// Returns a Vec of all updated indicators. This vec is ordered by Indicator index.
pub fn to_vec(&self) -> Vec<AgIndicator> {
let mut v = vec![];
v.extend(self.call.map(|i| AgIndicator::Call(i as u8)));
v.extend(self.callsetup.map(|i| AgIndicator::CallSetup(i as u8)));
v.extend(self.callheld.map(|i| AgIndicator::CallHeld(i as u8)));
v
}
}
#[cfg(test)]
mod tests {
use super::*;
use matches::assert_matches;
#[test]
fn update_hf_indicators_with_invalid_values_is_error() {
let mut hf_indicators = HfIndicators::default();
hf_indicators.enable_indicators(vec![
at::BluetoothHFIndicator::BatteryLevel,
at::BluetoothHFIndicator::EnhancedSafety,
]);
let battery_too_low = -18;
assert_matches!(
hf_indicators
.update_indicator_value(at::BluetoothHFIndicator::BatteryLevel, battery_too_low),
Err(())
);
let battery_too_high = 1243;
assert_matches!(
hf_indicators
.update_indicator_value(at::BluetoothHFIndicator::BatteryLevel, battery_too_high),
Err(())
);
let negative_safety = -1;
assert_matches!(
hf_indicators
.update_indicator_value(at::BluetoothHFIndicator::EnhancedSafety, negative_safety),
Err(())
);
let large_safety = 8;
assert_matches!(
hf_indicators
.update_indicator_value(at::BluetoothHFIndicator::EnhancedSafety, large_safety),
Err(())
);
}
#[test]
fn update_disabled_hf_indicators_with_valid_values_is_error() {
// Default is no indicators set. Therefore any updates are errors.
let mut hf_indicators = HfIndicators::default();
let valid_battery = 32;
assert_matches!(
hf_indicators
.update_indicator_value(at::BluetoothHFIndicator::BatteryLevel, valid_battery),
Err(())
);
let valid_safety = 0;
assert_matches!(
hf_indicators
.update_indicator_value(at::BluetoothHFIndicator::EnhancedSafety, valid_safety),
Err(())
);
}
#[test]
fn update_hf_indicators_with_valid_values_is_ok() {
let mut hf_indicators = HfIndicators::default();
// Default values.
assert_eq!(hf_indicators.enhanced_safety.value, None);
assert_eq!(hf_indicators.enhanced_safety.enabled, false);
assert_eq!(hf_indicators.battery_level.value, None);
assert_eq!(hf_indicators.battery_level.enabled, false);
// Enable both.
hf_indicators.enable_indicators(vec![
at::BluetoothHFIndicator::BatteryLevel,
at::BluetoothHFIndicator::EnhancedSafety,
]);
let valid_battery = 83;
assert_matches!(
hf_indicators
.update_indicator_value(at::BluetoothHFIndicator::BatteryLevel, valid_battery),
Ok(HfIndicator::BatteryLevel(83))
);
assert_eq!(hf_indicators.battery_level.value, Some(valid_battery as u8));
let valid_safety = 0;
assert_matches!(
hf_indicators
.update_indicator_value(at::BluetoothHFIndicator::EnhancedSafety, valid_safety),
Ok(HfIndicator::EnhancedSafety(false))
);
assert_eq!(hf_indicators.enhanced_safety.value, Some(false));
}
#[test]
fn default_indicators_reporting_is_disabled_with_all_indicators_enabled() {
let default = AgIndicatorsReporting::default();
assert!(!default.is_enabled);
assert!(default.service);
assert!(default.signal);
assert!(default.roam);
assert!(default.batt_chg);
}
#[test]
fn indicator_flags_are_updated_from_bool_flags() {
// No flags is OK, no updates.
let mut status = AgIndicatorsReporting::default();
let empty = vec![];
let expected = status.clone();
status.update_from_flags(empty);
assert_eq!(status, expected);
// An incomplete set of flags is OK (5 out of 7 supplied). The Call, Call Held, Call Setup
// flags will not be overridden.
let mut status = AgIndicatorsReporting::default();
let incomplete = vec![Some(false), None, Some(false), Some(true)];
let expected = AgIndicatorsReporting {
is_enabled: false,
service: false,
signal: true,
roam: true,
batt_chg: true,
};
status.update_from_flags(incomplete);
assert_eq!(status, expected);
// A typical set of flags (all 7) is OK.
let mut status = AgIndicatorsReporting::default();
let flags =
vec![None, Some(false), Some(false), Some(true), Some(false), Some(false), None];
let expected = AgIndicatorsReporting {
is_enabled: false,
service: true,
signal: false,
roam: false,
batt_chg: true,
};
status.update_from_flags(flags);
assert_eq!(status, expected);
// Too many flags provided is also OK. Per the spec, this can happen, and the excess flags
// are gracefully ignored.
let mut status = AgIndicatorsReporting::default();
let too_many =
vec![None, None, None, None, Some(true), Some(false), None, Some(true), Some(false)];
let expected = AgIndicatorsReporting {
is_enabled: false,
service: true,
signal: true,
roam: false,
batt_chg: true,
};
status.update_from_flags(too_many);
assert_eq!(status, expected);
}
#[test]
fn toggling_indicators_reporting_maintains_same_indicators() {
let mut status = AgIndicatorsReporting::default();
assert!(!status.is_enabled);
// Unset a specific indicator.
status.set_batt_chg(false);
// Toggling indicators reporting should preserve indicator values.
let expected1 = AgIndicatorsReporting { is_enabled: false, ..status.clone() };
status.disable();
assert_eq!(status, expected1);
status.disable();
assert_eq!(status, expected1);
status.enable();
let expected2 = AgIndicatorsReporting { is_enabled: true, ..expected1.clone() };
assert_eq!(status, expected2);
assert!(status.is_enabled);
}
#[test]
fn indicator_enabled_is_false_when_indicators_reporting_disabled() {
let status = AgIndicatorsReporting::new_disabled();
// Even though all the individual indicator values are toggled on, we expect
// `indicator_enabled` to return false because indicators reporting is disabled.
assert!(!status.indicator_enabled(&AgIndicator::Service(0)));
assert!(!status.indicator_enabled(&AgIndicator::Call(0)));
}
#[test]
fn indicator_enabled_check_returns_expected_result() {
let mut status = AgIndicatorsReporting::new_enabled();
status.batt_chg = false;
status.roam = false;
assert!(status.indicator_enabled(&AgIndicator::Service(0)));
assert!(status.indicator_enabled(&AgIndicator::Signal(0)));
assert!(status.indicator_enabled(&AgIndicator::Call(0)));
assert!(status.indicator_enabled(&AgIndicator::CallSetup(0)));
assert!(status.indicator_enabled(&AgIndicator::CallHeld(0)));
assert!(!status.indicator_enabled(&AgIndicator::Roam(0)));
assert!(!status.indicator_enabled(&AgIndicator::BatteryLevel(0)));
}
#[test]
fn find_call() {
let states = vec![];
let call = Call::find(states.into_iter());
assert_eq!(call, Call::None);
let states = vec![CallState::Terminated];
let call = Call::find(states.into_iter());
assert_eq!(call, Call::None);
let states = vec![CallState::OngoingActive];
let call = Call::find(states.into_iter());
assert_eq!(call, Call::Some);
let states = vec![CallState::OngoingHeld];
let call = Call::find(states.into_iter());
assert_eq!(call, Call::Some);
let states = vec![CallState::OngoingHeld, CallState::Terminated];
let call = Call::find(states.into_iter());
assert_eq!(call, Call::Some);
let states = vec![CallState::OngoingHeld, CallState::OngoingActive];
let call = Call::find(states.into_iter());
assert_eq!(call, Call::Some);
}
#[test]
fn find_callsetup() {
let states = vec![];
let setup = CallSetup::find(states.into_iter());
assert_eq!(setup, CallSetup::None);
let states = vec![CallState::Terminated];
let setup = CallSetup::find(states.into_iter());
assert_eq!(setup, CallSetup::None);
let states = vec![CallState::IncomingRinging];
let setup = CallSetup::find(states.into_iter());
assert_eq!(setup, CallSetup::Incoming);
let states = vec![CallState::IncomingWaiting];
let setup = CallSetup::find(states.into_iter());
assert_eq!(setup, CallSetup::Incoming);
let states = vec![CallState::OutgoingAlerting];
let setup = CallSetup::find(states.into_iter());
assert_eq!(setup, CallSetup::OutgoingAlerting);
let states = vec![CallState::OutgoingDialing];
let setup = CallSetup::find(states.into_iter());
assert_eq!(setup, CallSetup::OutgoingDialing);
// The first setup state is used.
let states = vec![CallState::OutgoingDialing, CallState::IncomingRinging];
let setup = CallSetup::find(states.into_iter());
assert_eq!(setup, CallSetup::OutgoingDialing);
// Other states have no effect
let states = vec![CallState::Terminated, CallState::IncomingRinging];
let setup = CallSetup::find(states.into_iter());
assert_eq!(setup, CallSetup::Incoming);
}
#[test]
fn find_call_held() {
let states = vec![];
let held = CallHeld::find(states.into_iter());
assert_eq!(held, CallHeld::None);
let states = vec![CallState::OngoingHeld];
let held = CallHeld::find(states.into_iter());
assert_eq!(held, CallHeld::Held);
// Active without Held is None.
let states = vec![CallState::OngoingActive];
let held = CallHeld::find(states.into_iter());
assert_eq!(held, CallHeld::None);
// Other states have no effect.
let states = vec![CallState::OngoingHeld, CallState::Terminated];
let held = CallHeld::find(states.into_iter());
assert_eq!(held, CallHeld::Held);
// Held then active produces expected result.
let states = vec![CallState::OngoingHeld, CallState::OngoingActive];
let held = CallHeld::find(states.into_iter());
assert_eq!(held, CallHeld::HeldAndActive);
// And so does the reverse.
let states = vec![CallState::OngoingActive, CallState::OngoingHeld];
let held = CallHeld::find(states.into_iter());
assert_eq!(held, CallHeld::HeldAndActive);
}
#[test]
fn find_call_indicators() {
let states = vec![];
let ind = CallIndicators::find(states.into_iter());
assert_eq!(ind, CallIndicators::default());
let states = vec![CallState::OngoingHeld, CallState::IncomingRinging];
let ind = CallIndicators::find(states.into_iter());
let expected = CallIndicators {
call: Call::Some,
callsetup: CallSetup::Incoming,
callheld: CallHeld::Held,
};
assert_eq!(ind, expected);
}
#[test]
fn call_indicators_differences() {
let a = CallIndicators::default();
let b = CallIndicators { ..a };
assert!(b.difference(a).is_empty());
let a = CallIndicators::default();
let b = CallIndicators { call: Call::Some, ..a };
let expected =
CallIndicatorsUpdates { call: Some(Call::Some), ..CallIndicatorsUpdates::default() };
assert_eq!(b.difference(a), expected);
let a = CallIndicators::default();
let b = CallIndicators { call: Call::Some, callheld: CallHeld::Held, ..a };
let expected = CallIndicatorsUpdates {
call: Some(Call::Some),
callheld: Some(CallHeld::Held),
..CallIndicatorsUpdates::default()
};
assert_eq!(b.difference(a), expected);
let a = CallIndicators { call: Call::Some, ..CallIndicators::default() };
let b = CallIndicators { callsetup: CallSetup::Incoming, ..a };
let expected = CallIndicatorsUpdates {
callsetup: Some(CallSetup::Incoming),
..CallIndicatorsUpdates::default()
};
assert_eq!(b.difference(a), expected);
}
#[test]
fn call_indicator_updates_is_empty() {
let mut updates = CallIndicatorsUpdates::default();
assert!(updates.is_empty());
updates.call = Some(Call::Some);
assert!(!updates.is_empty());
let mut updates = CallIndicatorsUpdates::default();
updates.callsetup = Some(CallSetup::Incoming);
assert!(!updates.is_empty());
let mut updates = CallIndicatorsUpdates::default();
updates.callheld = Some(CallHeld::Held);
assert!(!updates.is_empty());
}
#[test]
fn call_indicator_updates_to_vec() {
let mut updates = CallIndicatorsUpdates::default();
assert_eq!(updates.to_vec(), vec![]);
let call = Call::Some;
updates.call = Some(call);
assert_eq!(updates.to_vec(), vec![AgIndicator::Call(call as u8)]);
let callsetup = CallSetup::Incoming;
updates.callsetup = Some(callsetup);
let expected = vec![AgIndicator::Call(call as u8), AgIndicator::CallSetup(callsetup as u8)];
assert_eq!(updates.to_vec(), expected);
let callheld = CallHeld::Held;
updates.callheld = Some(callheld);
let expected = vec![
AgIndicator::Call(call as u8),
AgIndicator::CallSetup(callsetup as u8),
AgIndicator::CallHeld(callheld as u8),
];
assert_eq!(updates.to_vec(), expected);
}
}