blob: d7bfa4d3665dc4ec4141fd2f65b8885d05c9d133 [file] [log] [blame]
// Copyright 2018 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 crate::ime::{Ime, ImeState};
use failure::ResultExt;
use fidl::endpoints::{ClientEnd, RequestStream, ServerEnd};
use fidl_fuchsia_ui_input as uii;
use fuchsia_syslog::fx_log_err;
use futures::prelude::*;
use parking_lot::Mutex;
use std::sync::{Arc, Weak};
pub struct ImeServiceState {
pub keyboard_visible: bool,
pub active_ime: Option<Weak<Mutex<ImeState>>>,
pub visibility_listeners: Vec<uii::ImeVisibilityServiceControlHandle>,
}
/// The internal state of the IMEService, usually held behind an Arc<Mutex>
/// so it can be accessed from multiple places.
impl ImeServiceState {
pub fn update_keyboard_visibility(&mut self, visible: bool) {
self.keyboard_visible = visible;
self.visibility_listeners.retain(|listener| {
// drop listeners if they error on send
listener
.send_on_keyboard_visibility_changed(visible)
.is_ok()
});
}
}
/// The ImeService is a central, discoverable service responsible for creating new IMEs when a new
/// text field receives focus. It also advertises the ImeVisibilityService, which allows a client
/// (usually a soft keyboard container) to receive updates when the keyboard has been requested to
/// be shown or hidden.
#[derive(Clone)]
pub struct ImeService(Arc<Mutex<ImeServiceState>>);
impl ImeService {
pub fn new() -> ImeService {
ImeService(Arc::new(Mutex::new(ImeServiceState {
keyboard_visible: false,
active_ime: None,
visibility_listeners: Vec::new(),
})))
}
/// Only updates the keyboard visibility if IME passed in is active
pub fn update_keyboard_visibility_from_ime(
&self, check_ime: &Arc<Mutex<ImeState>>, visible: bool,
) {
let mut state = self.0.lock();
let active_ime_weak = match &state.active_ime {
Some(val) => val,
None => return,
};
let active_ime = match active_ime_weak.upgrade() {
Some(val) => val,
None => return,
};
if Arc::ptr_eq(check_ime, &active_ime) {
state.update_keyboard_visibility(visible);
}
}
fn get_input_method_editor(
&mut self, keyboard_type: uii::KeyboardType, action: uii::InputMethodAction,
initial_state: uii::TextInputState, client: ClientEnd<uii::InputMethodEditorClientMarker>,
editor: ServerEnd<uii::InputMethodEditorMarker>,
) {
if let Ok(client_proxy) = client.into_proxy() {
let ime = Ime::new(
keyboard_type,
action,
initial_state,
client_proxy,
self.clone(),
);
let mut state = self.0.lock();
state.active_ime = Some(ime.downgrade());
if let Ok(chan) = fuchsia_async::Channel::from_channel(editor.into_channel()) {
ime.bind_ime(chan);
}
}
}
pub fn show_keyboard(&self) {
self.0.lock().update_keyboard_visibility(true);
}
pub fn hide_keyboard(&self) {
self.0.lock().update_keyboard_visibility(false);
}
fn inject_input(&mut self, event: uii::InputEvent) {
let ime = {
let state = self.0.lock();
let active_ime_weak = match state.active_ime {
Some(ref v) => v,
None => return, // no currently active IME
};
match Ime::upgrade(active_ime_weak) {
Some(active_ime) => active_ime,
None => return, // IME no longer exists
}
};
ime.inject_input(event);
}
pub fn bind_ime_service(&self, chan: fuchsia_async::Channel) {
let mut self_clone = self.clone();
fuchsia_async::spawn(
async move {
let mut stream = uii::ImeServiceRequestStream::from_channel(chan);
while let Some(msg) = await!(stream.try_next())
.context("error reading value from IME service request stream")?
{
match msg {
uii::ImeServiceRequest::GetInputMethodEditor {
keyboard_type,
action,
initial_state,
client,
editor,
..
} => {
self_clone.get_input_method_editor(
keyboard_type,
action,
initial_state,
client,
editor,
);
}
uii::ImeServiceRequest::ShowKeyboard { .. } => {
self_clone.show_keyboard();
}
uii::ImeServiceRequest::HideKeyboard { .. } => {
self_clone.hide_keyboard();
}
uii::ImeServiceRequest::InjectInput { event, .. } => {
self_clone.inject_input(event);
}
}
}
Ok(())
}
.unwrap_or_else(|e: failure::Error| fx_log_err!("{:?}", e)),
);
}
pub fn bind_ime_visibility_service(&self, chan: fuchsia_async::Channel) {
let self_clone = self.clone();
fuchsia_async::spawn(
async move {
let stream = uii::ImeVisibilityServiceRequestStream::from_channel(chan);
let control_handle = stream.control_handle();
let mut state = self_clone.0.lock();
if control_handle
.send_on_keyboard_visibility_changed(state.keyboard_visible)
.is_ok()
{
state.visibility_listeners.push(control_handle);
}
Ok(())
}
.unwrap_or_else(|e: failure::Error| fx_log_err!("{:?}", e)),
);
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::ime::{HID_USAGE_KEY_ENTER, HID_USAGE_KEY_LEFT};
use crate::test_helpers::default_state;
use fidl;
use fidl_fuchsia_ui_input as uii;
use fuchsia_async as fasync;
use pin_utils::pin_mut;
fn async_service_test<T, F>(test_fn: T)
where
T: FnOnce(uii::ImeServiceProxy, uii::ImeVisibilityServiceProxy) -> F,
F: Future,
{
let mut executor = fasync::Executor::new()
.expect("Creating fuchsia_async executor for IME service tests failed");
let ime_service = ImeService::new();
let ime_service_proxy = {
let (service_proxy, service_server_end) =
fidl::endpoints::create_proxy::<uii::ImeServiceMarker>().unwrap();
let chan = fasync::Channel::from_channel(service_server_end.into_channel()).unwrap();
ime_service.bind_ime_service(chan);
service_proxy
};
let visibility_service_proxy = {
let (service_proxy, service_server_end) =
fidl::endpoints::create_proxy::<uii::ImeVisibilityServiceMarker>().unwrap();
let chan = fasync::Channel::from_channel(service_server_end.into_channel()).unwrap();
ime_service.bind_ime_visibility_service(chan);
service_proxy
};
let done = test_fn(ime_service_proxy, visibility_service_proxy);
pin_mut!(done);
// this will return a non-ready future if the tests stall
let res = executor.run_until_stalled(&mut done);
assert!(res.is_ready());
}
fn bind_ime_for_test(
ime_service: &uii::ImeServiceProxy,
) -> (
uii::InputMethodEditorProxy,
uii::InputMethodEditorClientRequestStream,
) {
let (ime_proxy, ime_server_end) =
fidl::endpoints::create_proxy::<uii::InputMethodEditorMarker>().unwrap();
let (editor_client_end, editor_request_stream) =
fidl::endpoints::create_request_stream().unwrap();
ime_service
.get_input_method_editor(
uii::KeyboardType::Text,
uii::InputMethodAction::Done,
&mut default_state(),
editor_client_end,
ime_server_end,
)
.unwrap();
(ime_proxy, editor_request_stream)
}
fn simulate_keypress(ime_service: &uii::ImeServiceProxy, code_point: u32, hid_usage: u32) {
ime_service
.inject_input(&mut uii::InputEvent::Keyboard(uii::KeyboardEvent {
event_time: 0,
device_id: 0,
phase: uii::KeyboardEventPhase::Pressed,
hid_usage: hid_usage,
code_point: code_point,
modifiers: 0,
}))
.unwrap();
ime_service
.inject_input(&mut uii::InputEvent::Keyboard(uii::KeyboardEvent {
event_time: 0,
device_id: 0,
phase: uii::KeyboardEventPhase::Released,
hid_usage: hid_usage,
code_point: code_point,
modifiers: 0,
}))
.unwrap();
}
#[test]
fn test_visibility_service_sends_updates() {
async_service_test(|ime_service, visibility_service| {
async move {
let mut ev_stream = visibility_service.take_event_stream();
// expect initial update with current status
let msg = await!(ev_stream.try_next())
.expect("expected working event stream")
.expect("visibility service should have sent message");
let uii::ImeVisibilityServiceEvent::OnKeyboardVisibilityChanged { visible } = msg;
assert_eq!(visible, false);
// expect asking for keyboard to reclose results in another message
ime_service.hide_keyboard().unwrap();
let msg = await!(ev_stream.try_next())
.expect("expected working event stream")
.expect("visibility service should have sent message");
let uii::ImeVisibilityServiceEvent::OnKeyboardVisibilityChanged { visible } = msg;
assert_eq!(visible, false);
// expect asking for keyboard to to open in another message
ime_service.show_keyboard().unwrap();
let msg = await!(ev_stream.try_next())
.expect("expected working event stream")
.expect("visibility service should have sent message");
let uii::ImeVisibilityServiceEvent::OnKeyboardVisibilityChanged { visible } = msg;
assert_eq!(visible, true);
// expect asking for keyboard to close/open from IME works
let (ime, _editor_stream) = bind_ime_for_test(&ime_service);
ime.hide().unwrap();
let msg = await!(ev_stream.try_next())
.expect("expected working event stream")
.expect("visibility service should have sent message");
let uii::ImeVisibilityServiceEvent::OnKeyboardVisibilityChanged { visible } = msg;
assert_eq!(visible, false);
ime.show().unwrap();
let msg = await!(ev_stream.try_next())
.expect("expected working event stream")
.expect("visibility service should have sent message");
let uii::ImeVisibilityServiceEvent::OnKeyboardVisibilityChanged { visible } = msg;
assert_eq!(visible, true);
}
});
}
#[test]
fn test_inject_input_updates_ime() {
async_service_test(|ime_service, _visibility_service| {
async move {
// expect asking for keyboard to close/open from IME works
let (_ime, mut editor_stream) = bind_ime_for_test(&ime_service);
// type 'a'
simulate_keypress(&ime_service, 'a'.into(), 0);
let msg = await!(editor_stream.try_next())
.expect("expected working event stream")
.expect("ime should have sent message");
if let uii::InputMethodEditorClientRequest::DidUpdateState {
state, event: _, ..
} = msg
{
assert_eq!(state.text, "a");
assert_eq!(state.selection.base, 1);
assert_eq!(state.selection.extent, 1);
} else {
panic!("request should be DidUpdateState");
}
// press left arrow
simulate_keypress(&ime_service, 0, HID_USAGE_KEY_LEFT);
let msg = await!(editor_stream.try_next())
.expect("expected working event stream")
.expect("ime should have sent message");
if let uii::InputMethodEditorClientRequest::DidUpdateState {
state, event: _, ..
} = msg
{
assert_eq!(state.text, "a");
assert_eq!(state.selection.base, 0);
assert_eq!(state.selection.extent, 0);
} else {
panic!("request should be DidUpdateState");
}
}
});
}
#[test]
fn test_inject_input_sends_action() {
async_service_test(|ime_service, _visibility_service| {
async move {
let (_ime, mut editor_stream) = bind_ime_for_test(&ime_service);
simulate_keypress(&ime_service, 0, HID_USAGE_KEY_ENTER);
let msg = await!(editor_stream.try_next())
.expect("expected working event stream")
.expect("ime should have sent message");
if let uii::InputMethodEditorClientRequest::OnAction { action, .. } = msg {
assert_eq!(action, uii::InputMethodAction::Done);
} else {
panic!("request should be OnAction");
}
}
})
}
}