blob: 873d2d97dedb53009a6236a7ef55e17275029469 [file] [log] [blame]
// Copyright 2022 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 anyhow::{format_err, Error};
use bitflags::bitflags;
use crate::config::HandsFreeFeatureSupport;
/// Codec IDs. See HFP 1.8, Section 10 / Appendix B.
pub const CVSD: u8 = 0x01;
pub const MSBC: u8 = 0x02;
pub type CallIdx = usize;
/// Action to perform a call related supplementary services. During a call, the following procedures
/// shall be available for the subscriber to control the operation of Call Waiting or Call Hold;
///
/// See 3GPP TS 22.030 v16.0.0 / ETSI TS 122.030 v16.0.0
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum CallHoldAction {
/// Releases all held calls or sets User Determined User Busy (UDUB) for a waiting call.
ReleaseAllHeld,
/// Releases all active calls (if any exist) and accepts the other (held or waiting) call.
ReleaseAllActive,
/// Releases call with specified CallIdx.
ReleaseSpecified(CallIdx),
/// Places all active calls (if any exist) on hold and accepts the other (held or waiting) call.
HoldActiveAndAccept,
/// Request private consultation mode with specified call (CallIdx). (Place all calls on hold
/// EXCEPT the call indicated by CallIdx.)
HoldAllExceptSpecified(CallIdx),
/// Adds a held call to the conversation.
AddCallToHeldConversation,
/// Connects two calls and disconnects the subscriber from both calls. (optional for the HF).
ExplicitCallTransfer,
}
bitflags! {
/// Bitmap defined in HFP v1.8, Section 4.35.1 for use with the "+BRSF" AT result code.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AgFeatures: i64 {
const THREE_WAY_CALLING = 0b00_0000_0000_0001;
const NR_EC = 0b00_0000_0000_0010;
const VR = 0b00_0000_0000_0100;
const IN_BAND_RING = 0b00_0000_0000_1000;
const ATTACH_A_NUMBER_TO_VOICE_TAG = 0b00_0000_0001_0000;
const REJECT_CALL = 0b00_0000_0010_0000;
const ENHANCED_CALL_STATUS = 0b00_0000_0100_0000;
const ENHANCED_CALL_CONTROL = 0b00_0000_1000_0000;
const EXTENDED_ERROR_RESULT_CODES = 0b00_0001_0000_0000;
const CODEC_NEGOTIATION = 0b00_0010_0000_0000;
const HF_INDICATORS = 0b00_0100_0000_0000;
const ESCO_S4 = 0b00_1000_0000_0000;
const EVR_STATUS = 0b01_0000_0000_0000;
const VR_TEXT = 0b10_0000_0000_0000;
}
}
bitflags! {
/// Bitmap defined in HFP v1.8, Section 4.35.1 for use with the "AT+BRSF" AT command.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HfFeatures: i64 {
const NR_EC = 0b00_0000_0000_0001;
const THREE_WAY_CALLING = 0b00_0000_0000_0010;
const CLI_PRESENTATION = 0b00_0000_0000_0100;
const VR_ACTIVATION = 0b00_0000_0000_1000;
const REMOTE_VOLUME_CONTROL = 0b00_0000_0001_0000;
const ENHANCED_CALL_STATUS = 0b00_0000_0010_0000;
const ENHANCED_CALL_CONTROL = 0b00_0000_0100_0000;
const CODEC_NEGOTIATION = 0b00_0000_1000_0000;
const HF_INDICATORS = 0b00_0001_0000_0000;
const ESCO_S4 = 0b00_0010_0000_0000;
const EVR_STATUS = 0b00_0100_0000_0000;
const VR_TEXT = 0b00_1000_0000_0000;
}
}
impl TryFrom<&str> for CallHoldAction {
type Error = Error;
fn try_from(cmd_str: &str) -> Result<Self, Self::Error> {
let feature = match cmd_str {
"0" => CallHoldAction::ReleaseAllHeld,
"1" => CallHoldAction::ReleaseAllActive,
"2" => CallHoldAction::HoldActiveAndAccept,
cmd if cmd_str.starts_with("1") => {
let idx = cmd[1..].parse::<usize>()?;
CallHoldAction::ReleaseSpecified(idx)
}
cmd if cmd_str.starts_with("2") => {
let idx = cmd[1..].parse::<usize>()?;
CallHoldAction::HoldAllExceptSpecified(idx)
}
"3" => CallHoldAction::AddCallToHeldConversation,
"4" => CallHoldAction::ExplicitCallTransfer,
_ => {
return Err(format_err!("Could not match command: {:?} to feature.", cmd_str));
}
};
Ok(feature)
}
}
impl From<HandsFreeFeatureSupport> for HfFeatures {
fn from(value: HandsFreeFeatureSupport) -> Self {
let mut this = Self::empty();
this.set(Self::NR_EC, value.ec_or_nr);
this.set(Self::THREE_WAY_CALLING, value.call_waiting_or_three_way_calling);
this.set(Self::CLI_PRESENTATION, value.cli_presentation_capability);
this.set(Self::VR_ACTIVATION, value.voice_recognition_activation);
this.set(Self::REMOTE_VOLUME_CONTROL, value.remote_volume_control);
this.set(Self::ENHANCED_CALL_STATUS, false);
this.set(Self::ENHANCED_CALL_CONTROL, false);
this.set(Self::CODEC_NEGOTIATION, value.wide_band_speech);
this.set(Self::HF_INDICATORS, false);
this.set(Self::ESCO_S4, true);
this.set(Self::EVR_STATUS, value.enhanced_voice_recognition);
this.set(Self::VR_TEXT, value.enhanced_voice_recognition_with_text);
this
}
}
/// Properly tries to parse the commands from the AG to retrieve the information about how the call
/// hold and multiparty services are supported. Will return an error if cannot parse one command
/// correctly.
pub fn extract_features_from_command(commands: &Vec<String>) -> Result<Vec<CallHoldAction>, Error> {
let mut features = Vec::new();
for command in commands {
let feature = command.as_str().try_into()?;
features.push(feature);
}
Ok(features)
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
#[fuchsia::test]
fn commands_properly_translates_to_features() {
let commands = vec![
String::from("0"),
String::from("1"),
String::from("2"),
String::from("11"),
String::from("22"),
String::from("3"),
String::from("4"),
];
let expected_results = vec![
CallHoldAction::ReleaseAllHeld,
CallHoldAction::ReleaseAllActive,
CallHoldAction::HoldActiveAndAccept,
CallHoldAction::ReleaseSpecified(1),
CallHoldAction::HoldAllExceptSpecified(2),
CallHoldAction::AddCallToHeldConversation,
CallHoldAction::ExplicitCallTransfer,
];
let result = extract_features_from_command(&commands);
assert_matches!(result, Ok(_));
assert_eq!(result.unwrap(), expected_results);
}
#[fuchsia::test]
fn error_when_invalid_index() {
let commands = vec![String::from("1A")];
let result = extract_features_from_command(&commands);
assert_matches!(result, Err(_));
}
#[fuchsia::test]
fn error_when_feature_cannot_be_matched() {
let commands = vec![String::from("5")];
let result = extract_features_from_command(&commands);
assert_matches!(result, Err(_));
}
#[fuchsia::test]
fn error_when_one_feature_invalid() {
// 5 is invalid command in list of valid features
let commands = vec![String::from("0"), String::from("1"), String::from("5")];
let result = extract_features_from_command(&commands);
assert_matches!(result, Err(_));
}
}