blob: 77e266d3e6bd09b42efc30ae5d6697b5ea422d16 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
pub mod state_machine;
pub mod uci_hmsgs;
pub mod uci_hrcv;
use crate::adaptation::UwbAdaptation;
use crate::error::UwbErr;
use crate::event_manager::EventManager;
use crate::uci::uci_hrcv::UciResponse;
use android_hardware_uwb::aidl::android::hardware::uwb::{
UwbEvent::UwbEvent, UwbStatus::UwbStatus,
};
use log::{error, info};
use tokio::runtime::{Builder, Runtime};
use tokio::sync::{mpsc, oneshot};
use tokio::{select, task};
use uwb_uci_packets::Packet;
pub type Result<T> = std::result::Result<T, UwbErr>;
pub type UciResponseHandle = oneshot::Sender<UciResponse>;
// TODO: Use real values for these enums.
// Commands sent from JNI.
#[derive(Debug)]
pub enum JNICommand {
UwaEnable,
UwaDisable(bool),
UwaSessionDeinit(u32),
UwaSessionGetCount,
UwaStartRange(u32),
UwaStopRange(u32),
UwaGetSessionState(u32),
UwaSessionUpdateMulticastList {
session_id: u32,
action: u8,
no_of_controlee: u8,
address_list: Vec<u8>,
sub_session_id_list: Vec<i32>,
},
UwaSetCountryCode {
code: Vec<u8>,
},
Exit,
}
// Commands sent from JNI, which blocks until it gets a response.
#[derive(Debug)]
pub enum BlockingJNICommand {
GetDeviceInfo,
UwaSessionInit(u32, u8),
}
// Responses from the HAL.
#[derive(Debug)]
pub enum HalCallback {
Event { event: UwbEvent, event_status: UwbStatus },
UciRsp(uci_hrcv::UciResponse),
UciNtf(uci_hrcv::UciNotification),
}
struct Driver {
adaptation: UwbAdaptation,
event_manager: EventManager,
cmd_receiver: mpsc::UnboundedReceiver<JNICommand>,
blocking_cmd_receiver: mpsc::UnboundedReceiver<(BlockingJNICommand, UciResponseHandle)>,
rsp_receiver: mpsc::UnboundedReceiver<HalCallback>,
response_channel: Option<UciResponseHandle>,
}
// Creates a future that handles messages from JNI and the HAL.
async fn drive(
adaptation: UwbAdaptation,
event_manager: EventManager,
cmd_receiver: mpsc::UnboundedReceiver<JNICommand>,
blocking_cmd_receiver: mpsc::UnboundedReceiver<(BlockingJNICommand, UciResponseHandle)>,
rsp_receiver: mpsc::UnboundedReceiver<HalCallback>,
) -> Result<()> {
Driver::new(adaptation, event_manager, cmd_receiver, blocking_cmd_receiver, rsp_receiver)
.drive()
.await
}
impl Driver {
fn new(
adaptation: UwbAdaptation,
event_manager: EventManager,
cmd_receiver: mpsc::UnboundedReceiver<JNICommand>,
blocking_cmd_receiver: mpsc::UnboundedReceiver<(BlockingJNICommand, UciResponseHandle)>,
rsp_receiver: mpsc::UnboundedReceiver<HalCallback>,
) -> Self {
Self {
adaptation,
event_manager,
cmd_receiver,
blocking_cmd_receiver,
rsp_receiver,
response_channel: None,
}
}
// Continually handles messages.
async fn drive(mut self) -> Result<()> {
loop {
self.drive_once().await?
}
}
// Handles a single message from JNI or the HAL.
async fn drive_once(&mut self) -> Result<()> {
// TODO: Handle messages for real instead of just logging them.
select! {
Some(cmd) = self.cmd_receiver.recv() => {
match cmd {
JNICommand::UwaEnable => {
log::info!("{:?}", cmd);
self.adaptation.initialize();
self.adaptation.hal_open();
self.adaptation.core_initialization()?;
},
JNICommand::UwaDisable(graceful) => log::info!("{:?}", cmd),
JNICommand::UwaSessionDeinit(session_id) => log::info!("{:?}", cmd),
JNICommand::UwaSessionGetCount => log::info!("{:?}", cmd),
JNICommand::UwaStartRange(session_id) => log::info!("{:?}", cmd),
JNICommand::UwaStopRange(session_id) => log::info!("{:?}", cmd),
JNICommand::UwaGetSessionState(session_id) => log::info!("{:?}", cmd),
JNICommand::UwaSessionUpdateMulticastList{session_id, action, no_of_controlee, ref address_list, ref sub_session_id_list} => log::info!("{:?}", cmd),
JNICommand::UwaSetCountryCode{ref code} => log::info!("{:?}", cmd),
JNICommand::Exit => return Err(UwbErr::Exit),
}
}
Some((cmd, tx)) = self.blocking_cmd_receiver.recv(), if self.response_channel.is_none() => {
// TODO: If we do something similar to communication to the HAL (using a channel
// to hide the asynchrony, we can remove the field and make this straightline code.
self.response_channel = Some(tx);
match cmd {
BlockingJNICommand::GetDeviceInfo => {
log::info!("BlockingJNICommand::GetDeviceInfo");
let bytes = uci_hmsgs::build_device_info_cmd().build().to_vec();
self.adaptation.send_uci_message(&bytes);
},
BlockingJNICommand::UwaSessionInit(session_id, session_type) => {
log::info!("{:?}", cmd);
let bytes = uci_hmsgs::build_session_init_cmd(session_id, session_type).build().to_vec();
self.adaptation.send_uci_message(&bytes);
}
}
}
Some(rsp) = self.rsp_receiver.recv() => {
match rsp {
HalCallback::Event{event, event_status} => {
log::info!("Received HAL event: {:?} with status: {:?}", event, event_status);
},
HalCallback::UciRsp(response) => {
self.response_channel.take().expect("the response channel does not exist").send(response);
},
HalCallback::UciNtf(response) => {
match response {
uci_hrcv::UciNotification::DeviceStatusNtf(response) => {
self.event_manager.device_status_notification_received(response);
},
uci_hrcv::UciNotification::GenericError(response) => {
self.event_manager.core_generic_error_notification_received(response);
},
uci_hrcv::UciNotification::SessionStatusNtf(response) => {
self.event_manager.session_status_notification_received(response);
},
uci_hrcv::UciNotification::ShortMacTwoWayRangeDataNtf(response) => {
self.event_manager.short_range_data_notification(response);
},
uci_hrcv::UciNotification::ExtendedMacTwoWayRangeDataNtf(response) => {
self.event_manager.extended_range_data_notification(response);
},
_ => log::warn!("Notification type not handled yet {:?}", response),
}
}
}
}
}
Ok(())
}
}
// Controller for sending tasks for the native thread to handle.
pub struct Dispatcher {
cmd_sender: mpsc::UnboundedSender<JNICommand>,
blocking_cmd_sender: mpsc::UnboundedSender<(BlockingJNICommand, UciResponseHandle)>,
join_handle: task::JoinHandle<Result<()>>,
runtime: Runtime,
}
impl Dispatcher {
pub fn new(event_manager: EventManager) -> Result<Dispatcher> {
info!("initializing dispatcher");
let (cmd_sender, cmd_receiver) = mpsc::unbounded_channel::<JNICommand>();
let (blocking_cmd_sender, blocking_cmd_receiver) =
mpsc::unbounded_channel::<(BlockingJNICommand, UciResponseHandle)>();
let (rsp_sender, rsp_receiver) = mpsc::unbounded_channel::<HalCallback>();
let adaptation = UwbAdaptation::new(None, rsp_sender);
// We create a new thread here both to avoid reusing the Java service thread and because
// binder threads will call into this.
let runtime =
Builder::new_multi_thread().worker_threads(1).thread_name("uwb-uci-handler").build()?;
let join_handle = runtime.spawn(drive(
adaptation,
event_manager,
cmd_receiver,
blocking_cmd_receiver,
rsp_receiver,
));
Ok(Dispatcher { cmd_sender, blocking_cmd_sender, join_handle, runtime })
}
pub fn send_jni_command(&self, cmd: JNICommand) -> Result<()> {
self.cmd_sender.send(cmd)?;
Ok(())
}
// TODO: Consider implementing these separate for different commands so we can have more
// specific return types.
pub fn block_on_jni_command(&self, cmd: BlockingJNICommand) -> Result<UciResponse> {
let (tx, rx) = oneshot::channel();
self.blocking_cmd_sender.send((cmd, tx))?;
Ok(self.runtime.block_on(rx)?)
}
fn exit(&mut self) -> Result<()> {
self.send_jni_command(JNICommand::Exit)?;
let _ = self.runtime.block_on(&mut self.join_handle);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_driver() -> Result<()> {
// TODO: Remove this once we call it somewhere real.
logger::init(
logger::Config::default().with_tag_on_device("uwb").with_min_level(log::Level::Error),
);
// TODO : Consider below ways to write the unit test
// 1
// Create test-only methods on EventManager that allow you to construct one without Java
// (and to have dummy/tracked effects when callbacks get called).
//
// 2 and recommended way
// Take the signature of EventManager and make it a trait, which would allow you to impl that
// trait again on a test-only mock type
//let mut dispatcher = Dispatcher::new()?;
//dispatcher.send_hal_response(HalCallback::A)?;
//dispatcher.send_jni_command(JNICommand::UwaEnable)?;
//dispatcher.block_on_jni_command(BlockingJNICommand::GetDeviceInfo)?;
//dispatcher.exit()?;
//assert!(dispatcher.send_hal_response(HalCallback::B).is_err());
Ok(())
}
}