/** * @file inactivity.c * Inactivity module -- this implements inactivity logic for MCE *

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

* @author David Weinehall * @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 "inactivity.h" #include "../mce.h" #include "../mce-log.h" #include "../mce-dbus.h" #include "../mce-dsme.h" #include "../mce-hbtimer.h" #include "../mce-setting.h" #ifdef ENABLE_WAKELOCKS # include "../libwakelock.h" #endif #include #include #include /* ========================================================================= * * CONSTANTS * ========================================================================= */ /** Module name */ #define MODULE_NAME "inactivity" /** 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 = 250 }; /** Maximum amount of monitored activity callbacks */ #define ACTIVITY_CB_MAX_MONITORED 16 /** Duration of suspend blocking after sending inactivity signals */ #define MIA_KEEPALIVE_DURATION_MS 5000 /* ========================================================================= * * PROTOTYPES * ========================================================================= */ /* ------------------------------------------------------------------------- * * HELPER_FUNCTIONS * ------------------------------------------------------------------------- */ static const char *mia_inactivity_repr (bool inactive); /* ------------------------------------------------------------------------- * * DBUS_ACTION * ------------------------------------------------------------------------- */ /** D-Bus action */ typedef struct { /** D-Bus activity callback owner */ gchar *owner; /** D-Bus service */ gchar *service; /** D-Bus path */ gchar *path; /** D-Bus interface */ gchar *interface; /** D-Bus method name */ gchar *method_name; } mia_action_t; static mia_action_t *mia_action_create (const char *owner, const char *service, const char *path, const char *interface, const char *method); static void mia_action_delete (mia_action_t *self); /* ------------------------------------------------------------------------- * * DATAPIPE_TRACKING * ------------------------------------------------------------------------- */ static bool mia_activity_allowed (void); static void mia_datapipe_inactivity_event_cb (gconstpointer data); static void mia_datapipe_device_inactive_cb (gconstpointer data); static void mia_datapipe_proximity_sensor_actual_cb(gconstpointer data); static void mia_datapipe_inactivity_delay_cb (gconstpointer data); static void mia_datapipe_submode_cb (gconstpointer data); static void mia_datapipe_alarm_ui_state_cb (gconstpointer data); static void mia_datapipe_call_state_cb (gconstpointer data); static void mia_datapipe_system_state_cb (gconstpointer data); static void mia_datapipe_display_state_next_cb (gconstpointer data); static void mia_datapipe_interaction_expected_cb (gconstpointer data); static void mia_datapipe_charger_state_cb (gconstpointer data); static void mia_datapipe_init_done_cb (gconstpointer data); static void mia_datapipe_osupdate_running_cb (gconstpointer data); static void mia_datapipe_check_initial_state (void); static void mia_datapipe_init(void); static void mia_datapipe_quit(void); /* ------------------------------------------------------------------------- * * SUSPEND_BLOCK * ------------------------------------------------------------------------- */ #ifdef ENABLE_WAKELOCKS static void mia_keepalive_rethink (void); static gboolean mia_keepalive_cb (gpointer aptr); static void mia_keepalive_start (void); static void mia_keepalive_stop (void); #endif /* ------------------------------------------------------------------------- * * DBUS_HANDLERS * ------------------------------------------------------------------------- */ static gboolean mia_dbus_activity_action_owner_cb (DBusMessage *const sig); static gboolean mia_dbus_add_activity_action_cb (DBusMessage *const msg); static gboolean mia_dbus_remove_activity_action_cb (DBusMessage *const msg); static gboolean mia_dbus_send_inactivity_state (DBusMessage *const method_call); static gboolean mia_dbus_get_inactivity_state (DBusMessage *const req); static void mia_dbus_init(void); static void mia_dbus_quit(void); /* ------------------------------------------------------------------------- * * ACTIVITY_ACTIONS * ------------------------------------------------------------------------- */ static void mia_activity_action_remove (const char *owner); static bool mia_activity_action_add (const char *owner, const char *service, const char *path, const char *interface, const char *method); static void mia_activity_action_remove_all (void); static void mia_activity_action_execute_all (void); /* ------------------------------------------------------------------------- * * INACTIVITY_TIMER * ------------------------------------------------------------------------- */ static gboolean mia_timer_cb (gpointer data); static void mia_timer_start (void); static void mia_timer_stop (void); static void mia_timer_init (void); static void mia_timer_quit (void); /* ------------------------------------------------------------------------- * * SHUTDOWN_TIMER * ------------------------------------------------------------------------- */ static gboolean mia_shutdown_timer_cb (gpointer aptr); static void mia_shutdown_timer_stop (void); static void mia_shutdown_timer_start (void); static bool mia_shutdown_timer_wanted (void); static void mia_shutdown_timer_rethink (void); static void mia_shutdown_timer_restart (void); static void mia_shutdown_timer_init (void); static void mia_shutdown_timer_quit (void); /* ------------------------------------------------------------------------- * * SETTING_TRACKING * ------------------------------------------------------------------------- */ static void mia_setting_changed_cb (GConfClient *gcc, guint id, GConfEntry *entry, gpointer data); static void mia_setting_init (void); static void mia_setting_quit (void); /* ------------------------------------------------------------------------- * * MODULE_LOAD_UNLOAD * ------------------------------------------------------------------------- */ G_MODULE_EXPORT const gchar *g_module_check_init (GModule *module); G_MODULE_EXPORT void g_module_unload (GModule *module); /* ------------------------------------------------------------------------- * * STATE_DATA * ------------------------------------------------------------------------- */ /** List of activity callbacks */ static GSList *activity_action_list = NULL; /** List of monitored activity requesters */ static GSList *activity_action_owners = NULL; /** Heartbeat timer for inactivity timeout */ static mce_hbtimer_t *inactivity_timer_hnd = 0; /** Heartbeat timer for idle shutdown */ static mce_hbtimer_t *shutdown_timer_hnd = 0; /** Flag for: Idle shutdown already triggered */ static bool shutdown_timer_triggered = false; /** Cached device inactivity state * * Default to inactive. Initial state is evaluated and broadcast over * D-Bus during mce startup - see mia_datapipe_check_initial_state(). */ static gboolean device_inactive = TRUE; /* Cached submode bitmask; assume in transition at startup */ static submode_t submode = MCE_SUBMODE_TRANSITION; /** Cached alarm ui state */ static alarm_ui_state_t alarm_ui_state = MCE_ALARM_UI_INVALID_INT32; /** Cached call state */ static call_state_t call_state = CALL_STATE_INVALID; /* Cached system state */ static system_state_t system_state = MCE_SYSTEM_STATE_UNDEF; /** Cached display state */ static display_state_t display_state_next = MCE_DISPLAY_UNDEF; /** Cached inactivity timeout delay [s] */ static gint device_inactive_delay = DEFAULT_INACTIVITY_DELAY; /** Cached proximity sensor state */ static cover_state_t proximity_sensor_actual = COVER_UNDEF; /** Cached Interaction expected state */ static bool interaction_expected = false; /** Cached charger state; assume unknown */ static charger_state_t charger_state = CHARGER_STATE_UNDEF; /** Cached init_done state; assume unknown */ static tristate_t init_done = TRISTATE_UNKNOWN; /** Update mode is active; assume false */ static bool osupdate_running = false; /** Setting for automatick shutdown delay after inactivity */ static gint mia_shutdown_delay = MCE_DEFAULT_INACTIVITY_SHUTDOWN_DELAY; static guint mia_shutdown_delay_setting_id = 0; /* ========================================================================= * * HELPER_FUNCTIONS * ========================================================================= */ /** Inactivity boolean to human readable string helper */ static const char *mia_inactivity_repr(bool inactive) { return inactive ? "inactive" : "active"; } /* ========================================================================= * * DBUS_ACTION * ========================================================================= */ /** Create D-Bus action object * * @param owner Private D-Bus name of the owner of the action * @param service D-Bus name of the service to send message to * @param path Object path to use * @param interface Inteface to use * @param method Name of the method to invoke * * @return pointer to initialized D-Bus action object */ static mia_action_t *mia_action_create(const char *owner, const char *service, const char *path, const char *interface, const char *method) { mia_action_t *self = g_malloc0(sizeof *self); self->owner = g_strdup(owner); self->service = g_strdup(service); self->path = g_strdup(path); self->interface = g_strdup(interface); self->method_name = g_strdup(method); return self; } /** Delete D-Bus action object * * @param self Pointer to initialized D-Bus action object, or NULL */ static void mia_action_delete(mia_action_t *self) { if( !self ) goto EXIT; g_free(self->owner); g_free(self->service); g_free(self->path); g_free(self->interface); g_free(self->method_name); g_free(self); EXIT: return; } /* ========================================================================= * * DATAPIPE_TRACKING * ========================================================================= */ static bool mia_activity_allowed(void) { bool allowed = false; /* Never filter activity if display is in dimmed state. * * Whether we have arrived to dimmed state via expected or * unexpected routes, the touch input is active and ui side * event eater will ignore only the first event. If we do * not allow activity (and turn on the display) we will get * ui interaction in odd looking dimmed state that then gets * abruptly ended by blanking timer. */ if( display_state_next == MCE_DISPLAY_DIM ) goto ALLOW; /* Activity applies only when display is on */ if( display_state_next != MCE_DISPLAY_ON ) { mce_log(LL_DEBUG, "display_state_curr = %s; ignoring activity", display_state_repr(display_state_next)); goto DENY; } /* Activity applies only to USER mode */ if( system_state != MCE_SYSTEM_STATE_USER ) { mce_log(LL_DEBUG, "system_state = %s; ignoring activity", system_state_repr(system_state)); goto DENY; } /* Normally activity does not apply when lockscreen is active */ if( submode & MCE_SUBMODE_TKLOCK ) { /* Active alarm */ switch( alarm_ui_state ) { case MCE_ALARM_UI_RINGING_INT32: case MCE_ALARM_UI_VISIBLE_INT32: goto ALLOW; default: break; } /* Active call */ switch( call_state ) { case CALL_STATE_RINGING: case CALL_STATE_ACTIVE: goto ALLOW; default: break; } /* Expecting user interaction */ if( interaction_expected ) goto ALLOW; goto DENY; } ALLOW: allowed = true; DENY: return allowed; } /** React to device inactivity requests * * @param data The unfiltered inactivity state; * TRUE if the device is inactive, * FALSE if the device is active */ static void mia_datapipe_inactivity_event_cb(gconstpointer data) { gboolean inactive = GPOINTER_TO_INT(data); mce_log(LL_DEBUG, "input: inactivity=%s", mia_inactivity_repr(inactive)); if( inactive ) { /* Inactivity is not repeated */ if( device_inactive ) goto EXIT; } else { /* Activity might not be allowed */ if( !mia_activity_allowed() ) goto EXIT; } datapipe_exec_full(&device_inactive_pipe, GINT_TO_POINTER(inactive)); EXIT: return; } /** React to device inactivity changes * * @param data Filtered inactivity state; * TRUE if the device is inactive, * FALSE if the device is active */ static void mia_datapipe_device_inactive_cb(gconstpointer data) { gboolean prev = device_inactive; device_inactive = GPOINTER_TO_INT(data); /* Actions taken on transitions only */ if( prev != device_inactive ) { mce_log(LL_DEBUG, "device_inactive: %s -> %s", mia_inactivity_repr(prev), mia_inactivity_repr(device_inactive)); mia_dbus_send_inactivity_state(NULL); /* React to activity */ if( !device_inactive ) mia_activity_action_execute_all(); mia_shutdown_timer_rethink(); } /* Restart/stop timer */ mia_timer_start(); } /** Generate activity from proximity sensor uncover * * @param data proximity sensor state as void pointer */ static void mia_datapipe_proximity_sensor_actual_cb(gconstpointer data) { cover_state_t prev = proximity_sensor_actual; proximity_sensor_actual = GPOINTER_TO_INT(data); if( proximity_sensor_actual == prev ) goto EXIT; mce_log(LL_DEBUG, "proximity_sensor_actual: %s -> %s", proximity_state_repr(prev), proximity_state_repr(proximity_sensor_actual)); /* generate activity if proximity sensor is * uncovered and there is a incoming call */ if( proximity_sensor_actual == COVER_OPEN && call_state == CALL_STATE_RINGING ) { mce_log(LL_INFO, "proximity -> uncovered, call = ringing"); mce_datapipe_generate_activity(); } EXIT: return; } /** React to inactivity timeout change * * @param data inactivity timeout (as void pointer) */ static void mia_datapipe_inactivity_delay_cb(gconstpointer data) { gint prev = device_inactive_delay; device_inactive_delay = GPOINTER_TO_INT(data); /* Sanitise timeout */ if( device_inactive_delay <= 0 ) device_inactive_delay = 30; if( device_inactive_delay == prev ) goto EXIT; mce_log(LL_DEBUG, "device_inactive_delay: %d -> %d", prev, device_inactive_delay); /* Reprogram timer */ mia_timer_start(); EXIT: return; } /** Handle submode_pipe notifications * * @param data The submode stored in a pointer */ static void mia_datapipe_submode_cb(gconstpointer data) { submode_t prev = submode; submode = GPOINTER_TO_INT(data); if( submode == prev ) goto EXIT; mce_log(LL_DEBUG, "submode = %s", submode_change_repr(prev, submode)); EXIT: return; } /** Handle alarm_ui_state_pipe notifications * * @param data (not used) */ static void mia_datapipe_alarm_ui_state_cb(gconstpointer data) { alarm_ui_state_t prev = alarm_ui_state; alarm_ui_state = GPOINTER_TO_INT(data); if( alarm_ui_state == prev ) goto EXIT; mce_log(LL_DEBUG, "alarm_ui_state: %s -> %s", alarm_state_repr(prev), alarm_state_repr(alarm_ui_state)); EXIT: return; } /** Handle call_state_pipe notifications * * @param data (not used) */ static void mia_datapipe_call_state_cb(gconstpointer data) { call_state_t prev = call_state; call_state = GPOINTER_TO_INT(data); if( call_state == prev ) goto EXIT; mce_log(LL_DEBUG, "call_state = %s", call_state_repr(call_state)); EXIT: return; } /** * Handle system_state_pipe notifications * * @param data The system state stored in a pointer */ static void mia_datapipe_system_state_cb(gconstpointer data) { system_state_t prev = system_state; system_state = GPOINTER_TO_INT(data); if( system_state == prev ) goto EXIT; mce_log(LL_DEBUG, "system_state: %s -> %s", system_state_repr(prev), system_state_repr(system_state)); if( prev == MCE_SYSTEM_STATE_UNDEF ) mia_datapipe_check_initial_state(); mia_shutdown_timer_rethink(); EXIT: return; } /** Handle display_state_next_pipe notifications * * @param data Current display_state_t (as void pointer) */ static void mia_datapipe_display_state_next_cb(gconstpointer data) { display_state_t prev = display_state_next; display_state_next = GPOINTER_TO_INT(data); if( display_state_next == prev ) goto EXIT; mce_log(LL_DEBUG, "display_state_next: %s -> %s", display_state_repr(prev), display_state_repr(display_state_next)); if( prev == MCE_DISPLAY_UNDEF ) mia_datapipe_check_initial_state(); EXIT: return; } /** Change notifications for interaction_expected_pipe */ static void mia_datapipe_interaction_expected_cb(gconstpointer data) { bool prev = interaction_expected; interaction_expected = GPOINTER_TO_INT(data); if( prev == interaction_expected ) goto EXIT; mce_log(LL_DEBUG, "interaction_expected: %d -> %d", prev, interaction_expected); /* Generate activity to restart blanking timers if interaction * becomes expected while lockscreen is active. */ if( interaction_expected && (submode & MCE_SUBMODE_TKLOCK) && display_state_next == MCE_DISPLAY_ON ) { mce_log(LL_DEBUG, "interaction expected; generate activity"); mce_datapipe_generate_activity(); } EXIT: return; } /** Change notifications for charger_state */ static void mia_datapipe_charger_state_cb(gconstpointer data) { charger_state_t prev = charger_state; charger_state = GPOINTER_TO_INT(data); if( charger_state == prev ) goto EXIT; mce_log(LL_DEBUG, "charger_state = %s -> %s", charger_state_repr(prev), charger_state_repr(charger_state)); mia_shutdown_timer_rethink(); EXIT: return; } /** Change notifications for init_done */ static void mia_datapipe_init_done_cb(gconstpointer data) { tristate_t prev = init_done; init_done = GPOINTER_TO_INT(data); if( init_done == prev ) goto EXIT; mce_log(LL_DEBUG, "init_done = %s -> %s", tristate_repr(prev), tristate_repr(init_done)); mia_shutdown_timer_rethink(); EXIT: return; } /** Change notifications for osupdate_running */ static void mia_datapipe_osupdate_running_cb(gconstpointer data) { bool prev = osupdate_running; osupdate_running = GPOINTER_TO_INT(data); if( osupdate_running == prev ) goto EXIT; mce_log(LL_DEBUG, "osupdate_running = %d -> %d", prev, osupdate_running); mia_shutdown_timer_rethink(); EXIT: return; } /** Handle initial state evaluation and broadcast */ static void mia_datapipe_check_initial_state(void) { static bool done = false; /* This must be done only once */ if( done ) goto EXIT; /* Wait until the initial state transitions on * mce startup are done and the device state * is sufficiently known */ if( system_state == MCE_SYSTEM_STATE_UNDEF ) goto EXIT; if( display_state_next == MCE_DISPLAY_UNDEF ) goto EXIT; done = true; /* Basically the idea is that mce restarts while the * display is off should leave the device in inactive * state, but booting up / restarting mce while the * display is on should yield active state. * * Once mce startup has progressed so that we known * the system state: Attempt to generate activity. * * The activity filtering rules should take care * of suppressing it in the "mce restart while * display is off" case. */ mce_log(LL_DEBUG, "device state known"); mce_datapipe_generate_activity(); /* Make sure the current state gets broadcast even * if the artificial activity gets suppressed. */ mce_log(LL_DEBUG, "forced broadcast"); mia_dbus_send_inactivity_state(0); EXIT: return; } /** Array of datapipe handlers */ static datapipe_handler_t mia_datapipe_handlers[] = { // output triggers { .datapipe = &inactivity_event_pipe, .output_cb = mia_datapipe_inactivity_event_cb, }, { .datapipe = &device_inactive_pipe, .output_cb = mia_datapipe_device_inactive_cb, }, { .datapipe = &proximity_sensor_actual_pipe, .output_cb = mia_datapipe_proximity_sensor_actual_cb, }, { .datapipe = &inactivity_delay_pipe, .output_cb = mia_datapipe_inactivity_delay_cb, }, { .datapipe = &submode_pipe, .output_cb = mia_datapipe_submode_cb, }, { .datapipe = &alarm_ui_state_pipe, .output_cb = mia_datapipe_alarm_ui_state_cb, }, { .datapipe = &call_state_pipe, .output_cb = mia_datapipe_call_state_cb, }, { .datapipe = &system_state_pipe, .output_cb = mia_datapipe_system_state_cb, }, { .datapipe = &display_state_next_pipe, .output_cb = mia_datapipe_display_state_next_cb, }, { .datapipe = &interaction_expected_pipe, .output_cb = mia_datapipe_interaction_expected_cb, }, { .datapipe = &charger_state_pipe, .output_cb = mia_datapipe_charger_state_cb, }, { .datapipe = &init_done_pipe, .output_cb = mia_datapipe_init_done_cb, }, { .datapipe = &osupdate_running_pipe, .output_cb = mia_datapipe_osupdate_running_cb, }, // sentinel { .datapipe = 0, } }; static datapipe_bindings_t mia_datapipe_bindings = { .module = "inactivity", .handlers = mia_datapipe_handlers, }; /** Append triggers/filters to datapipes */ static void mia_datapipe_init(void) { mce_datapipe_init_bindings(&mia_datapipe_bindings); } /** Remove triggers/filters from datapipes */ static void mia_datapipe_quit(void) { mce_datapipe_quit_bindings(&mia_datapipe_bindings); } /* ========================================================================= * * SUSPEND_BLOCK * ========================================================================= */ #ifdef ENABLE_WAKELOCKS /** Timer ID for ending suspend blocking */ static guint mia_keepalive_id = 0; /** Evaluate need for suspend blocking */ static void mia_keepalive_rethink(void) { static bool have_lock = false; bool need_lock = (mia_keepalive_id != 0); if( have_lock == need_lock ) goto EXIT; mce_log(LL_DEBUG, "inactivity notify wakelock: %s", need_lock ? "OBTAIN" : "RELEASE"); if( (have_lock = need_lock) ) wakelock_lock("mce_inactivity_notify", -1); else wakelock_unlock("mce_inactivity_notify"); EXIT: return; } /** Timer callback for ending suspend blocking */ static gboolean mia_keepalive_cb(gpointer aptr) { (void)aptr; mia_keepalive_id = 0; mia_keepalive_rethink(); return FALSE; } /** Start/restart temporary suspend blocking */ static void mia_keepalive_start(void) { if( mia_keepalive_id ) { g_source_remove(mia_keepalive_id), mia_keepalive_id = 0; } mia_keepalive_id = g_timeout_add(MIA_KEEPALIVE_DURATION_MS, mia_keepalive_cb, 0); mia_keepalive_rethink(); } /** Cancel suspend blocking */ static void mia_keepalive_stop(void) { if( mia_keepalive_id ) { g_source_remove(mia_keepalive_id), mia_keepalive_id = 0; } mia_keepalive_rethink(); } #endif /* ENABLE_WAKELOCKS */ /* ========================================================================= * * DBUS_HANDLERS * ========================================================================= */ /** D-Bus name owner changed handler for canceling activity actions * * If a process that has added activity actions drops out from the system * bus, the actions must be canceled. * * @param sig NameOwnerChanged D-Bus signal * * @return TRUE */ static gboolean mia_dbus_activity_action_owner_cb(DBusMessage *const sig) { DBusError err = DBUS_ERROR_INIT; const char *name = 0; const char *prev = 0; const char *curr = 0; if( !dbus_message_get_args(sig, &err, DBUS_TYPE_STRING, &name, DBUS_TYPE_STRING, &prev, DBUS_TYPE_STRING, &curr, DBUS_TYPE_INVALID)) { mce_log(LL_ERR, "Failed to get arguments: %s: %s", err.name, err.message); goto EXIT; } if( !*curr ) mia_activity_action_remove(name); EXIT: dbus_error_free(&err); return TRUE; } /** D-Bus callback for the add activity callback method call * * @param req The D-Bus message * * @return TRUE */ static gboolean mia_dbus_add_activity_action_cb(DBusMessage *const req) { const char *sender = dbus_message_get_sender(req); DBusError err = DBUS_ERROR_INIT; const char *service = 0; const char *path = 0; const char *interface = 0; const char *method = 0; dbus_bool_t res = false; if( !sender ) goto EXIT; mce_log(LL_DEVEL, "Add activity callback request from %s", mce_dbus_get_name_owner_ident(sender)); if( !dbus_message_get_args(req, &err, DBUS_TYPE_STRING, &service, DBUS_TYPE_STRING, &path, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &method, DBUS_TYPE_INVALID) ) { mce_log(LL_ERR, "Failed to get arguments: %s: %s", err.name, err.message); goto EXIT; } res = mia_activity_action_add(sender, service, path, interface, method); EXIT: if( !dbus_message_get_no_reply(req) ) { DBusMessage *rsp = dbus_new_method_reply(req); if( !dbus_message_append_args(rsp, DBUS_TYPE_BOOLEAN, &res, DBUS_TYPE_INVALID) ) { mce_log(LL_ERR, "Failed to append reply argument"); dbus_message_unref(rsp); } else { dbus_send_message(rsp); } } dbus_error_free(&err); return TRUE; } /** D-Bus callback for the remove activity callback method call * * @param req The D-Bus message * * @return TRUE */ static gboolean mia_dbus_remove_activity_action_cb(DBusMessage *const req) { const char *sender = dbus_message_get_sender(req); if( !sender ) goto EXIT; mce_log(LL_DEVEL, "Remove activity callback request from %s", mce_dbus_get_name_owner_ident(sender)); mia_activity_action_remove(sender); EXIT: if( !dbus_message_get_no_reply(req) ) { DBusMessage *reply = dbus_new_method_reply(req); dbus_send_message(reply); } return TRUE; } /** Send an inactivity status reply or signal * * @param method_call A DBusMessage to reply to; * pass NULL to send an inactivity status signal instead * @return TRUE */ static gboolean mia_dbus_send_inactivity_state(DBusMessage *const method_call) { /* Make sure initial state is broadcast; -1 does not match TRUE/FALSE */ static int last_sent = -1; DBusMessage *msg = NULL; if( method_call ) { /* Send reply to state query */ msg = dbus_new_method_reply(method_call); } else if( last_sent == device_inactive ) { /* Do not repeat broadcasts */ goto EXIT; } else { /* Broadcast state change */ #ifdef ENABLE_WAKELOCKS /* Block suspend for a while to give other processes * a chance to get and process the signal. */ mia_keepalive_start(); #endif msg = dbus_new_signal(MCE_SIGNAL_PATH, MCE_SIGNAL_IF, MCE_INACTIVITY_SIG); } mce_log(method_call ? LL_DEBUG : LL_DEVEL, "Sending inactivity %s: %s", method_call ? "reply" : "signal", mia_inactivity_repr(device_inactive)); /* Append the inactivity status */ if( !dbus_message_append_args(msg, DBUS_TYPE_BOOLEAN, &device_inactive, DBUS_TYPE_INVALID) ) { mce_log(LL_ERR, "Failed to append argument to D-Bus message"); goto EXIT; } /* Send the message */ dbus_send_message(msg), msg = 0; if( !method_call ) last_sent = device_inactive; EXIT: if( msg ) dbus_message_unref(msg); return TRUE; } /** D-Bus callback for the get inactivity status method call * * @param req The D-Bus message * * @return TRUE */ static gboolean mia_dbus_get_inactivity_state(DBusMessage *const req) { mce_log(LL_DEVEL, "Received inactivity status get request from %s", mce_dbus_get_message_sender_ident(req)); /* Try to send a reply that contains the current inactivity status */ mia_dbus_send_inactivity_state(req); return TRUE; } /** Array of dbus message handlers */ static mce_dbus_handler_t mia_dbus_handlers[] = { /* signals - outbound (for Introspect purposes only) */ { .interface = MCE_SIGNAL_IF, .name = MCE_INACTIVITY_SIG, .type = DBUS_MESSAGE_TYPE_SIGNAL, .args = " \n" }, /* method calls */ { .interface = MCE_REQUEST_IF, .name = MCE_INACTIVITY_STATUS_GET, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = mia_dbus_get_inactivity_state, .args = " \n" }, { .interface = MCE_REQUEST_IF, .name = MCE_ADD_ACTIVITY_CALLBACK_REQ, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = mia_dbus_add_activity_action_cb, .args = " \n" " \n" " \n" " \n" " \n" }, { .interface = MCE_REQUEST_IF, .name = MCE_REMOVE_ACTIVITY_CALLBACK_REQ, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = mia_dbus_remove_activity_action_cb, .args = "" }, /* sentinel */ { .interface = 0 } }; /** Add dbus handlers */ static void mia_dbus_init(void) { mce_dbus_handler_register_array(mia_dbus_handlers); } /** Remove dbus handlers */ static void mia_dbus_quit(void) { mce_dbus_handler_unregister_array(mia_dbus_handlers); } /* ========================================================================= * * ACTIVITY_ACTIONS * ========================================================================= */ /** * Remove an activity cb from the list of monitored processes * and the callback itself * * @param owner The D-Bus owner of the callback */ static void mia_activity_action_remove(const char *owner) { /* Remove D-Bus name owner monitor */ mce_dbus_owner_monitor_remove(owner, &activity_action_owners); /* Remove the activity callback itself */ for( GSList *now = activity_action_list; now; now = now->next ) { mia_action_t *cb = now->data; if( strcmp(cb->owner, owner) ) continue; activity_action_list = g_slist_remove(activity_action_list, cb); mia_action_delete(cb); break; }; } static bool mia_activity_action_add(const char *owner, const char *service, const char *path, const char *interface, const char *method) { bool ack = false; /* Add D-Bus name owner monitor */ if( mce_dbus_owner_monitor_add(owner, mia_dbus_activity_action_owner_cb, &activity_action_owners, ACTIVITY_CB_MAX_MONITORED) == -1) { mce_log(LL_ERR, "Failed to add name owner monitoring for `%s'", owner); goto EXIT; } /* Add activity callback */ mia_action_t *cb = mia_action_create(owner, service, path, interface, method); activity_action_list = g_slist_prepend(activity_action_list, cb); ack = true; EXIT: return ack; } /** Unregister all activity callbacks */ static void mia_activity_action_remove_all(void) { for( GSList *now = activity_action_list; now; now = now->next ) { mia_action_t *act = now->data; now->data = 0; mia_action_delete(act); } /* Flush action list */ g_slist_free(activity_action_list), activity_action_list = 0; /* Remove associated name owner monitors */ mce_dbus_owner_monitor_remove_all(&activity_action_owners); } /** Call all activity callbacks, then unregister them */ static void mia_activity_action_execute_all(void) { /* Execute D-Bus actions */ for( GSList *now = activity_action_list; now; now = now->next ) { mia_action_t *act = now->data; dbus_send(act->service, act->path, act->interface, act->method_name, 0, DBUS_TYPE_INVALID); } /* Then unregister them */ mia_activity_action_remove_all(); } /* ========================================================================= * * INACTIVITY_TIMER * ========================================================================= */ /** Timer callback to trigger inactivity * * @param data (not used) * * @return Always returns FALSE, to disable the timeout */ static gboolean mia_timer_cb(gpointer data) { (void)data; mce_log(LL_DEBUG, "inactivity timeout triggered"); mce_datapipe_generate_inactivity(); return FALSE; } /** Setup inactivity timeout */ static void mia_timer_start(void) { mia_timer_stop(); if( device_inactive ) goto EXIT; mce_log(LL_DEBUG, "inactivity timeout in %d seconds", device_inactive_delay); mce_hbtimer_set_period(inactivity_timer_hnd, device_inactive_delay * 1000); mce_hbtimer_start(inactivity_timer_hnd); EXIT: return; } /** Cancel inactivity timeout */ static void mia_timer_stop(void) { if( mce_hbtimer_is_active(inactivity_timer_hnd) ) { mce_log(LL_DEBUG, "inactivity timeout canceled"); mce_hbtimer_stop(inactivity_timer_hnd); } } /** Initialize inactivity heartbeat timer */ static void mia_timer_init(void) { inactivity_timer_hnd = mce_hbtimer_create("inactivity-timer", device_inactive_delay * 1000, mia_timer_cb, 0); } /** Cleanup inactivity heartbeat timer */ static void mia_timer_quit(void) { mce_hbtimer_delete(inactivity_timer_hnd), inactivity_timer_hnd = 0; } /* ========================================================================= * * SHUTDOWN_TIMER * ========================================================================= */ /** Timer callback for starting inactivity shutdown * * @param aptr (unused) User data pointer * * @return TRUE to restart timer, or FALSE to stop it */ static gboolean mia_shutdown_timer_cb(gpointer aptr) { (void)aptr; mce_log(LL_WARN, "shutdown timer triggered"); shutdown_timer_triggered = true; mce_dsme_request_normal_shutdown(); return FALSE; } /** Cancel inactivity shutdown timeout */ static void mia_shutdown_timer_stop(void) { if( !mce_hbtimer_is_active(shutdown_timer_hnd) ) goto EXIT; mce_log(LL_DEBUG, "shutdown timer stopped"); mce_hbtimer_stop(shutdown_timer_hnd); EXIT: shutdown_timer_triggered = false; return; } /** Schedule inactivity shutdown timeout */ static void mia_shutdown_timer_start(void) { if( mce_hbtimer_is_active(shutdown_timer_hnd) ) goto EXIT; if( shutdown_timer_triggered ) { mce_log(LL_DEBUG, "shutdown timer already triggered"); goto EXIT; } if( mia_shutdown_delay < MCE_MINIMUM_INACTIVITY_SHUTDOWN_DELAY ) { mce_log(LL_DEBUG, "shutdown timer is disabled in config"); goto EXIT; } mce_log(LL_DEBUG, "shutdown timer started (trigger in %d seconds)", mia_shutdown_delay); mce_hbtimer_set_period(shutdown_timer_hnd, mia_shutdown_delay * 1000); mce_hbtimer_start(shutdown_timer_hnd); EXIT: return; } /** Evaluate if conditions for inactivity shutdown has been met * * @return true if delayed shutdown should be started, false otherwise */ static bool mia_shutdown_timer_wanted(void) { bool want_timer = false; if( !device_inactive ) goto EXIT; if( charger_state != CHARGER_STATE_OFF ) goto EXIT; if( osupdate_running ) goto EXIT; if( init_done != TRISTATE_TRUE ) goto EXIT; if( system_state != MCE_SYSTEM_STATE_USER ) goto EXIT; want_timer = true; EXIT: return want_timer; } /** Schedule/cancel inactivity shutdown based on device state */ static void mia_shutdown_timer_rethink(void) { if( mia_shutdown_timer_wanted() ) mia_shutdown_timer_start(); else mia_shutdown_timer_stop(); } /** Reschedule inactivity shutdown after settings changes */ static void mia_shutdown_timer_restart(void) { if( !shutdown_timer_triggered ) { mia_shutdown_timer_stop(); mia_shutdown_timer_rethink(); } } /** Initialize inactivity shutdown triggering */ static void mia_shutdown_timer_init(void) { shutdown_timer_hnd = mce_hbtimer_create("idle_shutdown", mia_shutdown_delay * 1000, mia_shutdown_timer_cb, 0); } /** Cleanup inactivity shutdown triggering */ static void mia_shutdown_timer_quit(void) { mce_hbtimer_delete(shutdown_timer_hnd), shutdown_timer_hnd = 0; } /* ========================================================================= * * SETTING_TRACKING * ========================================================================= */ /** Handle setting value changed notifications * * @param gcc (unused) gconf client object * @param id ID from gconf_client_notify_add() * @param entry The modified GConf entry * @param aptr (unused) User data pointer */ static void mia_setting_changed_cb(GConfClient *gcc, guint id, GConfEntry *entry, gpointer aptr) { (void)gcc; (void)aptr; const GConfValue *gcv = gconf_entry_get_value(entry); if( !gcv ) { mce_log(LL_DEBUG, "GConf Key `%s' has been unset", gconf_entry_get_key(entry)); goto EXIT; } if( id == mia_shutdown_delay_setting_id ) { gint prev = mia_shutdown_delay; mia_shutdown_delay = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "mia_shutdown_delay: %d -> %d", prev, mia_shutdown_delay); mia_shutdown_timer_restart(); } else { mce_log(LL_WARN, "Spurious GConf value received; confused!"); } EXIT: return; } /** Get intial setting values and start tracking changes */ static void mia_setting_init(void) { mce_setting_track_int(MCE_SETTING_INACTIVITY_SHUTDOWN_DELAY, &mia_shutdown_delay, MCE_DEFAULT_INACTIVITY_SHUTDOWN_DELAY, mia_setting_changed_cb, &mia_shutdown_delay_setting_id); } /** Stop tracking setting changes */ static void mia_setting_quit(void) { mce_setting_notifier_remove(mia_shutdown_delay_setting_id), mia_shutdown_delay_setting_id = 0; } /* ========================================================================= * * MODULE_LOAD_UNLOAD * ========================================================================= */ /** Init function for the inactivity module * * @param module (not used) * * @return NULL on success, a string with an error message on failure */ const gchar *g_module_check_init(GModule *module) { (void)module; mia_setting_init(); mia_timer_init(); mia_shutdown_timer_init(); /* Append triggers/filters to datapipes */ mia_datapipe_init(); /* Add dbus handlers */ mia_dbus_init(); /* Start timers */ mia_timer_start(); /* The initial inactivity state gets broadcast once the system * and display states are known */ return NULL; } /** Exit function for the inactivity module * * @todo D-Bus unregistration * * @param module (not used) */ void g_module_unload(GModule *module) { (void)module; mia_setting_quit(); /* Remove dbus handlers */ mia_dbus_quit(); /* Remove triggers/filters from datapipes */ mia_datapipe_quit(); /* Do not leave any timers active */ mia_timer_quit(); mia_shutdown_timer_quit(); #ifdef ENABLE_WAKELOCKS mia_keepalive_stop(); #endif /* Flush activity actions */ mia_activity_action_remove_all(); return; }