/** * @file proximity.c * Proximity sensor module *

* Copyright © 2010-2011 Nokia Corporation and/or its subsidiary(-ies). * Copyright (C) 2012-2019 Jolla Ltd. *

* @author David Weinehall * @author Tuomo Tanskanen * @author Tapio Rantala * @author Santtu Lakkala * @author Simo Piiroinen * * mce is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * mce is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mce. If not, see . */ #include "proximity.h" #include "../mce.h" #include "../mce-lib.h" #include "../mce-log.h" #include "../mce-setting.h" #include "../mce-sensorfw.h" #include /* ========================================================================= * * Macros * ========================================================================= */ /** Module name */ #define MODULE_NAME "proximity" /* ========================================================================= * * Protos * ========================================================================= */ /* ------------------------------------------------------------------------- * * MP_MONITOR * ------------------------------------------------------------------------- */ static gboolean mp_monitor_linger_end_cb (gpointer aptr); static void mp_monitor_cancel_linger (void); static void mp_monitor_start_linger (void); static gboolean mp_monitor_enable_delay_cb (gpointer aptr); static void mp_monitor_report_state (void); static void mp_monitor_update_state (bool covered); static void mp_monitor_set_enabled (bool enable); static bool mp_monitor_on_demand (void); static void mp_monitor_rethink (void); /* ------------------------------------------------------------------------- * * MP_SETTING * ------------------------------------------------------------------------- */ static void mp_setting_cb (GConfClient *const gcc, const guint id, GConfEntry *const entry, gpointer const data); static void mp_setting_init(void); static void mp_setting_quit(void); /* ------------------------------------------------------------------------- * * MP_DATAPIPE * ------------------------------------------------------------------------- */ static void mp_datapipe_set_proximity_sensor_actual (cover_state_t state); static void mp_datapipe_set_lid_sensor_actual (cover_state_t state); static void mp_datapipe_call_state_cb (gconstpointer const data); static void mp_datapipe_alarm_ui_state_cb (gconstpointer const data); static void mp_datapipe_display_state_next_cb (gconstpointer data); static void mp_datapipe_display_state_curr_cb (gconstpointer data); static void mp_datapipe_submode_cb (gconstpointer data); static void mp_datapipe_uiexception_type_cb (gconstpointer data); static void mp_datapipe_proximity_sensor_required_cb(gconstpointer data); static void mp_datapipe_init (void); static void mp_datapipe_quit (void); /* ------------------------------------------------------------------------- * * G_MODULE * ------------------------------------------------------------------------- */ const gchar *g_module_check_init(GModule *module); void g_module_unload (GModule *module); /* ========================================================================= * * Data * ========================================================================= */ /** Functionality provided by this module */ static const gchar *const provides[] = { MODULE_NAME, NULL }; /** Module information */ G_MODULE_EXPORT module_info_struct module_info = { /** Name of the module */ .name = MODULE_NAME, /** Module provides */ .provides = provides, /** Module priority */ .priority = 100 }; /** State of proximity sensor monitoring */ static bool mp_monitor_enabled = FALSE; /** Linger time for disabling sensor in on-demand mode [ms] */ static gint mp_monitor_linger_end_ms = 5000; /** Timer id for disabling sensor in on-demand mode */ static guint mp_monitor_linger_end_id = 0; /** Time reserved for sensor power up to reach stable state [ms] */ static gint mp_monitor_enable_delay_ms = 500; /** Timer id for ending sensor power up delay */ static guint mp_monitor_enable_delay_id = 0; /** Currently active on-demand reasons */ static GHashTable *mp_datapipe_proximity_sensor_required_lut = 0; /* ------------------------------------------------------------------------- * * datapipes * ------------------------------------------------------------------------- */ /** Cached proximity_sensor_required_pipe state */ static bool proximity_sensor_required = false; /** Cached call_state_pipe state */ static call_state_t call_state = CALL_STATE_INVALID; /** Cached alarm_ui_state_pipe state */ static alarm_ui_state_t alarm_ui_state = MCE_ALARM_UI_INVALID_INT32; /** Cached display_state_next_pipe state */ static display_state_t display_state_next = MCE_DISPLAY_UNDEF; /** Cached display_state_curr_pipe state */ static display_state_t display_state_curr = MCE_DISPLAY_UNDEF; /** Cached submode_pipe state */ static submode_t submode = MCE_SUBMODE_NORMAL; /** Cached uiexception_type_pipe state */ static uiexception_type_t uiexception_type = UIEXCEPTION_TYPE_NONE; /* ------------------------------------------------------------------------- * * settings * ------------------------------------------------------------------------- */ /** Configuration value for use proximity sensor */ static gboolean setting_use_ps = MCE_DEFAULT_PROXIMITY_PS_ENABLED; static guint setting_use_ps_conf_id = 0; /** Configuration value for on-demand proximity sensor use */ static gboolean setting_on_demand_ps = MCE_DEFAULT_PROXIMITY_ON_DEMAND; static guint setting_on_demand_ps_conf_id = 0; /** Configuration value for ps acts as lid sensor */ static gboolean setting_ps_acts_as_lid = MCE_DEFAULT_PROXIMITY_PS_ACTS_AS_LID; static guint setting_ps_acts_as_lid_conf_id = 0; /* ========================================================================= * * MP_MONITOR * ========================================================================= */ static gboolean mp_monitor_linger_end_cb(gpointer aptr) { (void)aptr; mce_log(LL_DEBUG, "PS monitoring: linger timeout"); mp_monitor_linger_end_id = 0; mp_monitor_set_enabled(false); return G_SOURCE_REMOVE; } static void mp_monitor_cancel_linger(void) { if( mp_monitor_linger_end_id ) { mce_log(LL_DEBUG, "PS monitoring: linger stopped"); g_source_remove(mp_monitor_linger_end_id), mp_monitor_linger_end_id = 0; } } static void mp_monitor_start_linger(void) { if( !mp_monitor_linger_end_id ) { mce_log(LL_DEBUG, "PS monitoring: linger started"); mp_monitor_linger_end_id = mce_wakelocked_timeout_add(mp_monitor_linger_end_ms, mp_monitor_linger_end_cb, 0); } } static gboolean mp_monitor_enable_delay_cb(gpointer aptr) { (void)aptr; mce_log(LL_DEBUG, "PS monitoring: sensor power up finished"); mp_monitor_enable_delay_id = 0; mp_monitor_report_state(); return G_SOURCE_REMOVE; } static cover_state_t proximity_sensor_actual = COVER_UNDEF; static void mp_monitor_report_state(void) { /* Skip if the sensor is not fully powered up yet */ if( !mp_monitor_enabled || mp_monitor_enable_delay_id ) goto EXIT; cover_state_t state = proximity_sensor_actual; if( setting_ps_acts_as_lid ) mp_datapipe_set_lid_sensor_actual(state); else mp_datapipe_set_proximity_sensor_actual(state); EXIT: return; } /** Callback for handling proximity sensor state changes * * @param covered proximity sensor covered */ static void mp_monitor_update_state(bool covered) { mce_log(LL_DEBUG, "PS monitoring: %s from sensorfwd", covered ? "COVERED" : "NOT-COVERED"); if( covered ) proximity_sensor_actual = COVER_CLOSED; else proximity_sensor_actual = COVER_OPEN; mp_monitor_report_state(); } /** Enable / disable proximity monitoring */ static void mp_monitor_set_enabled(bool enable) { mp_monitor_cancel_linger(); if( mp_monitor_enabled == enable ) goto EXIT; if( mp_monitor_enable_delay_id ) { g_source_remove(mp_monitor_enable_delay_id), mp_monitor_enable_delay_id = 0; } if( (mp_monitor_enabled = enable) ) { mce_log(LL_DEBUG, "PS monitoring: start sensor power up"); /* Start sensor power up hold-out timer */ mp_monitor_enable_delay_id = mce_wakelocked_timeout_add(mp_monitor_enable_delay_ms, mp_monitor_enable_delay_cb, 0); /* Install input processing hooks, update current state */ mce_sensorfw_ps_set_notify(mp_monitor_update_state); mce_sensorfw_ps_enable(); } else { mce_log(LL_DEBUG, "PS monitoring: sensor power down"); /* disable input */ mce_sensorfw_ps_disable(); /* remove input processing hooks */ mce_sensorfw_ps_set_notify(0); /* Artificially flip to unknown state */ if( setting_ps_acts_as_lid ) mp_datapipe_set_lid_sensor_actual(COVER_UNDEF); else mp_datapipe_set_proximity_sensor_actual(COVER_UNDEF); } EXIT: return; } /** Evaluate whether there is demand for proximity sensor */ static bool mp_monitor_on_demand(void) { /* Assume proximity state is not needed */ bool enable = false; /* LPM states are controlled by proximity sensor */ switch( display_state_next ) { case MCE_DISPLAY_LPM_ON: case MCE_DISPLAY_LPM_OFF: enable = true; break; default: break; } switch( display_state_curr ) { case MCE_DISPLAY_LPM_ON: case MCE_DISPLAY_LPM_OFF: enable = true; break; default: break; } /* Unblank on ringing / in-call blank/unblank */ switch( call_state ) { case CALL_STATE_RINGING: case CALL_STATE_ACTIVE: enable = true; break; default: break; } /* Unblank on alarm */ switch( alarm_ui_state ) { case MCE_ALARM_UI_RINGING_INT32: case MCE_ALARM_UI_VISIBLE_INT32: enable = true; break; default: break; } /* All exceptional display on conditions require * proximity information. */ if( uiexception_type != UIEXCEPTION_TYPE_NONE ) enable = true; /* Need for proximity sensor, as signaled via * proximity_sensor_required_pipe */ if( proximity_sensor_required ) enable = true; return enable; } /** Re-evaluate need for proximity monitoring */ static void mp_monitor_rethink(void) { mce_log(LL_DEBUG, "setting_use_ps=%d setting_on_demand_ps=%d", setting_use_ps, setting_on_demand_ps); if( !setting_use_ps ) { /* Disable without delay */ mp_monitor_set_enabled(false); /* As there will be no updates, feign value * appropriate for target datapipe. */ if( setting_ps_acts_as_lid ) mp_datapipe_set_lid_sensor_actual(COVER_UNDEF); else mp_datapipe_set_proximity_sensor_actual(COVER_OPEN); } else if( !setting_on_demand_ps ) { /* Enable without delay and wait for sensor data */ mp_monitor_set_enabled(true); } else { /* Act on demand */ bool enable = mp_monitor_on_demand(); mce_log(LL_DEBUG, "enable=%d enabled=%d", enable, mp_monitor_enabled); if( enable ) { /* Enable without delay, wait for data */ mp_monitor_set_enabled(true); } else if( mp_monitor_enabled ) { /* Disable after delay, then switch to COVER_UNDEF */ mp_monitor_start_linger(); } } } /* ========================================================================= * * MP_SETTING * ========================================================================= */ /** GConf callback for use proximity sensor setting * * @param gcc (not used) * @param id Connection ID from gconf_client_notify_add() * @param entry The modified GConf entry * @param data (not used) */ static void mp_setting_cb(GConfClient *const gcc, const guint id, GConfEntry *const entry, gpointer const data) { (void)gcc; (void)data; const GConfValue *gcv; if( !(gcv = gconf_entry_get_value(entry)) ) { mce_log(LL_WARN, "GConf value removed; confused!"); goto EXIT; } if( id == setting_use_ps_conf_id ) { gboolean prev = setting_use_ps; setting_use_ps = gconf_value_get_bool(gcv); if( prev != setting_use_ps ) { /* If sensor was disabled, we need to clear feigned * targer datapipe values. */ if( setting_use_ps ) { if( setting_ps_acts_as_lid ) mp_datapipe_set_lid_sensor_actual(COVER_UNDEF); else mp_datapipe_set_proximity_sensor_actual(COVER_UNDEF); } mp_monitor_rethink(); } } else if( id == setting_on_demand_ps_conf_id ) { gboolean prev = setting_on_demand_ps; setting_on_demand_ps = gconf_value_get_bool(gcv); if( prev != setting_on_demand_ps ) mp_monitor_rethink(); } else if( id == setting_ps_acts_as_lid_conf_id ) { gboolean prev = setting_ps_acts_as_lid; setting_ps_acts_as_lid = gconf_value_get_bool(gcv); if( prev != setting_ps_acts_as_lid ) { /* Transfer exposed value to current target datapipe * and clear the previous target datapipe. */ if( setting_ps_acts_as_lid ) { /* ps is lid now */ cover_state_t curr = datapipe_get_gint(proximity_sensor_actual_pipe); mp_datapipe_set_proximity_sensor_actual(COVER_OPEN); mp_datapipe_set_lid_sensor_actual(curr); } else { /* ps is ps again */ cover_state_t curr = datapipe_get_gint(lid_sensor_actual_pipe); mp_datapipe_set_lid_sensor_actual(COVER_UNDEF); mp_datapipe_set_proximity_sensor_actual(curr); } } } else { mce_log(LL_WARN, "Spurious GConf value received; confused!"); } EXIT: return; } /** Start tracking dynamic settings */ static void mp_setting_init(void) { /* PS enabled setting */ mce_setting_track_bool(MCE_SETTING_PROXIMITY_PS_ENABLED, &setting_use_ps, MCE_DEFAULT_PROXIMITY_PS_ENABLED, mp_setting_cb, &setting_use_ps_conf_id); /* on-demand setting */ mce_setting_track_bool(MCE_SETTING_PROXIMITY_ON_DEMAND, &setting_on_demand_ps, MCE_DEFAULT_PROXIMITY_ON_DEMAND, mp_setting_cb, &setting_on_demand_ps_conf_id); /* PS acts as LID sensor */ mce_setting_track_bool(MCE_SETTING_PROXIMITY_PS_ACTS_AS_LID, &setting_ps_acts_as_lid, MCE_DEFAULT_PROXIMITY_PS_ACTS_AS_LID, mp_setting_cb, &setting_ps_acts_as_lid_conf_id); } /** Stop tracking dynamic settings */ static void mp_setting_quit(void) { mce_setting_notifier_remove(setting_use_ps_conf_id), setting_use_ps_conf_id = 0; mce_setting_notifier_remove(setting_use_ps_conf_id), setting_on_demand_ps_conf_id = 0; mce_setting_notifier_remove(setting_ps_acts_as_lid_conf_id), setting_ps_acts_as_lid_conf_id = 0; } /* ========================================================================= * * MP_DATAPIPE * ========================================================================= */ /** Broadcast proximity sensor state within MCE * * @param state COVER_CLOSED or COVER_OPEN */ static void mp_datapipe_set_proximity_sensor_actual(cover_state_t state) { /* Get current proximity datapipe value */ cover_state_t curr = datapipe_get_gint(proximity_sensor_actual_pipe); /* Execute datapipe if state has changed */ if( curr != state ) { mce_log(LL_CRUCIAL, "state: %s -> %s", cover_state_repr(curr), cover_state_repr(state)); datapipe_exec_full(&proximity_sensor_actual_pipe, GINT_TO_POINTER(state)); } } /** Broadcast faked lid sensor state within mce * * @param state COVER_CLOSED, COVER_OPEN or COVER_UNDEF */ static void mp_datapipe_set_lid_sensor_actual(cover_state_t state) { cover_state_t curr = datapipe_get_gint(lid_sensor_actual_pipe); if( state != curr ) { mce_log(LL_CRUCIAL, "state: %s -> %s", cover_state_repr(curr), cover_state_repr(state)); datapipe_exec_full(&lid_sensor_actual_pipe, GINT_TO_POINTER(state)); } } /** Handle call state change * * @param data The call state stored in a pointer */ static void mp_datapipe_call_state_cb(gconstpointer const data) { call_state_t prev = call_state; call_state = GPOINTER_TO_INT(data); if( prev == call_state ) goto EXIT; mce_log(LL_DEBUG, "call_state: %s -> %s", call_state_repr(prev), call_state_repr(call_state)); mp_monitor_rethink(); EXIT: return; } /** Handle alarm ui state change * * @param data The alarm state stored in a pointer */ static void mp_datapipe_alarm_ui_state_cb(gconstpointer const data) { alarm_ui_state_t prev = alarm_ui_state; alarm_ui_state = GPOINTER_TO_INT(data); if( prev == alarm_ui_state ) goto EXIT; mce_log(LL_DEBUG, "alarm_ui_state: %s -> %s", alarm_state_repr(prev), alarm_state_repr(alarm_ui_state)); mp_monitor_rethink(); EXIT: return; } /** Handle display state change * * @param data The display state stored in a pointer */ static void mp_datapipe_display_state_next_cb(gconstpointer data) { display_state_t prev = display_state_next; display_state_next = GPOINTER_TO_INT(data); if( prev == display_state_next ) goto EXIT; mce_log(LL_DEBUG, "display_state_next: %s -> %s", display_state_repr(prev), display_state_repr(display_state_next)); mp_monitor_rethink(); EXIT: return; } /** Handle display state change * * @param data The display state stored in a pointer */ static void mp_datapipe_display_state_curr_cb(gconstpointer data) { display_state_t prev = display_state_curr; display_state_curr = GPOINTER_TO_INT(data); if( prev == display_state_curr ) goto EXIT; mce_log(LL_DEBUG, "display_state_curr: %s -> %s", display_state_repr(prev), display_state_repr(display_state_curr)); mp_monitor_rethink(); EXIT: return; } /** Handle submode change * * @param data The submode stored in a pointer */ static void mp_datapipe_submode_cb(gconstpointer data) { submode_t prev = submode; submode = GPOINTER_TO_INT(data); if( prev == submode ) goto EXIT; mce_log(LL_DEBUG, "submode: %s", submode_change_repr(prev, submode)); EXIT: return; } /** Change notifications for uiexception_type */ static void mp_datapipe_uiexception_type_cb(gconstpointer data) { uiexception_type_t prev = uiexception_type; uiexception_type = GPOINTER_TO_INT(data); if( uiexception_type == prev ) goto EXIT; mce_log(LL_DEBUG, "uiexception_type = %s -> %s", uiexception_type_repr(prev), uiexception_type_repr(uiexception_type)); mp_monitor_rethink(); EXIT: return; } /** Input notifications for proximity_sensor_required_pipe */ static void mp_datapipe_proximity_sensor_required_cb(gconstpointer data) { /* Assumption: The tags are statically allocated const strings */ const char *tag = data; if( !tag ) goto EXIT; mce_log(LL_DEBUG, "proximity_sensor_required: %s", tag); if( !mp_datapipe_proximity_sensor_required_lut ) goto EXIT; switch( *tag++ ){ case '+': g_hash_table_add(mp_datapipe_proximity_sensor_required_lut, (gpointer)tag); break; case '-': g_hash_table_remove(mp_datapipe_proximity_sensor_required_lut, (gpointer)tag); break; default: goto EXIT; } bool prev = proximity_sensor_required; proximity_sensor_required = g_hash_table_size(mp_datapipe_proximity_sensor_required_lut) != 0; if( prev == proximity_sensor_required ) goto EXIT; mce_log(LL_DEBUG, "proximity_sensor_required: %d -> %d", prev, proximity_sensor_required); mp_monitor_rethink(); EXIT: return; } /** Array of datapipe handlers */ static datapipe_handler_t mp_datapipe_handlers[] = { // input triggers { .datapipe = &proximity_sensor_required_pipe, .input_cb = mp_datapipe_proximity_sensor_required_cb, }, // output triggers { .datapipe = &call_state_pipe, .output_cb = mp_datapipe_call_state_cb, }, { .datapipe = &alarm_ui_state_pipe, .output_cb = mp_datapipe_alarm_ui_state_cb, }, { .datapipe = &display_state_next_pipe, .output_cb = mp_datapipe_display_state_next_cb, }, { .datapipe = &display_state_curr_pipe, .output_cb = mp_datapipe_display_state_curr_cb, }, { .datapipe = &submode_pipe, .output_cb = mp_datapipe_submode_cb, }, { .datapipe = &uiexception_type_pipe, .output_cb = mp_datapipe_uiexception_type_cb, }, // sentinel { .datapipe = 0, } }; static datapipe_bindings_t mp_datapipe_bindings = { .module = MODULE_NAME, .handlers = mp_datapipe_handlers, }; /** Append triggers/filters to datapipes */ static void mp_datapipe_init(void) { if( !mp_datapipe_proximity_sensor_required_lut ) { /* Assumption: Set of statically allocated const strings */ mp_datapipe_proximity_sensor_required_lut = g_hash_table_new(g_str_hash, g_str_equal); } mce_datapipe_init_bindings(&mp_datapipe_bindings); } /** Remove triggers/filters from datapipes */ static void mp_datapipe_quit(void) { if( mp_datapipe_proximity_sensor_required_lut ) { g_hash_table_unref(mp_datapipe_proximity_sensor_required_lut), mp_datapipe_proximity_sensor_required_lut = 0; } mce_datapipe_quit_bindings(&mp_datapipe_bindings); } /* ========================================================================= * * G_MODULE * ========================================================================= */ /** Init function for the proximity sensor module * * @param module Unused * * @return NULL on success, a string with an error message on failure */ G_MODULE_EXPORT const gchar * g_module_check_init(GModule *module) { (void)module; /* Append triggers/filters to datapipes */ mp_datapipe_init(); /* Get settings and start tracking changes */ mp_setting_init(); /* If the proximity sensor input is used for toggling * lid state, we must take care not to leave proximity * tracking to covered/unknown state. */ if( setting_ps_acts_as_lid ) mp_datapipe_set_proximity_sensor_actual(COVER_OPEN); /* enable/disable sensor based on initial conditions */ mp_monitor_rethink(); return NULL; } /** Exit function for the proximity sensor module * * @param module Unused */ G_MODULE_EXPORT void g_module_unload(GModule *module) { (void)module; /* Stop tracking setting changes */ mp_setting_quit(); /* Remove triggers/filters from datapipes */ mp_datapipe_quit(); /* Disable proximity monitoring to remove callbacks * to unloaded module */ mp_monitor_set_enabled(false); return; }