/** * @file tklock.c * This file implements the touchscreen/keypad lock component * of the Mode Control Entity *

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

* @author David Weinehall * @author Tapio Rantala * @author Santtu Lakkala * @author Jukka Turunen * @author Irina Bezruk * @author Kalle Jokiniemi * @author Mika Laitio * @author Markus Lehtonen * @author Simo Piiroinen * @author Vesa Halttunen * @author Andrew den Exter * * 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 "tklock.h" #include "mce-common.h" #include "mce-log.h" #include "mce-lib.h" #include "mce-io.h" #include "mce-setting.h" #include "mce-dbus.h" #include "mce-hbtimer.h" #include "evdev.h" #ifdef ENABLE_WAKELOCKS # include "libwakelock.h" #endif #include "modules/doubletap.h" #include "modules/display.h" #include "systemui/dbus-names.h" #include "systemui/tklock-dbus-names.h" #include #include #include #include #include #include #include #include typedef enum { /** No autorelock triggers */ AUTORELOCK_NO_TRIGGERS, /** Autorelock on keyboard slide closed */ AUTORELOCK_KBD_SLIDE, /** Autorelock on lens cover */ AUTORELOCK_LENS_COVER, } autorelock_t; /** Helper for evaluation number of items in an array */ #define numof(a) (sizeof(a)/sizeof*(a)) /* ========================================================================= * * CONSTANTS * ========================================================================= */ #define MODULE_NAME "tklock" /** Max valid time_t value in milliseconds */ #define MAX_TICK (INT_MAX * (int64_t)1000) /** Min valid time_t value in milliseconds */ #define MIN_TICK 0 /** Maximum number of concurrent notification ui exceptions */ #define TKLOCK_NOTIF_SLOTS 32 /** How long to wait for lid close after low lux [ms] */ #define TKLOCK_LIDFILTER_SET_WAIT_FOR_CLOSE_DELAY 1500 /** How long to wait for low lux after lid close [ms] */ #define TKLOCK_LIDFILTER_SET_WAIT_FOR_DARK_DELAY 1200 /** How long to wait for high lux after lid open [ms] */ #define TKLOCK_LIDFILTER_SET_WAIT_FOR_LIGHT_DELAY 1200 /* ========================================================================= * * DATATYPES * ========================================================================= */ typedef struct { /** BOOTTIME tick when notification autostops */ int64_t ns_until; /** Amount of ms autostop extends from user input */ int64_t ns_renew; /** Private D-Bus name of the slot owner */ gchar *ns_owner; /** Assumed unique identification string */ gchar *ns_name; } tklock_notif_slot_t; typedef struct { /** Array of notification slots */ tklock_notif_slot_t tn_slot[TKLOCK_NOTIF_SLOTS]; /** BOOTTIME linger tick from deactivated slots */ int64_t tn_linger; /** Timer id for autostopping notification slots */ guint tn_autostop_id; /** Slot owner D-Bus name monitoring list */ GSList *tn_monitor_list; } tklock_notif_state_t; /** Proximity sensor history */ typedef struct { /** Monotonic timestamp, ms resolution */ int64_t tick; /** Proximity sensor state */ cover_state_t state; } ps_history_t; /** Ambient light lux value mapped into enumerated states * * In case the lid sensor can't be trusted for some reason, data from * ambient light sensor heuristics can be used for avoiding incorrect * blank/unblank actions. * * For this purpose the raw data from ambient light sensor is tracked * and mapped in to three states: * * - TKLOCK_LIDLIGHT_NA: The data from als is not applicable for filtering. * - TKLOCK_LIDLIGHT_LO: The als indicates darkness. * - TKLOCK_LIDLIGHT_HI: The als indicates some amount of light. */ typedef enum { /* Light level is not applicable for state evaluation */ TKLOCK_LIDLIGHT_NA, /* Light level equals complete darkness */ TKLOCK_LIDLIGHT_LO, /* Light level equals at least some light */ TKLOCK_LIDLIGHT_HI, } tklock_lidlight_t; /* ========================================================================= * * PROTOTYPES * ========================================================================= */ // datapipe values and triggers static void tklock_datapipe_system_state_cb(gconstpointer data); static void tklock_datapipe_devicelock_state_cb(gconstpointer data); static void tklock_datapipe_devicelock_state_cb2(gpointer aptr); static void tklock_datapipe_resume_detected_event_cb(gconstpointer data); static void tklock_datapipe_devicelock_service_state_cb(gconstpointer data); static void tklock_datapipe_lipstick_service_state_cb(gconstpointer data); static void tklock_datapipe_osupdate_running_cb(gconstpointer data); static void tklock_datapipe_shutting_down_cb(gconstpointer data); static void tklock_datapipe_display_state_curr_cb(gconstpointer data); static void tklock_datapipe_display_state_next_cb(gconstpointer data); static void tklock_datapipe_proximity_eval_led(void); static void tklock_datapipe_proximity_update(void); static gboolean tklock_datapipe_proximity_uncover_cb(gpointer data); static void tklock_datapipe_proximity_uncover_cancel(void); static void tklock_datapipe_proximity_uncover_schedule(void); static void tklock_datapipe_proximity_sensor_actual_cb(gconstpointer data); static void tklock_datapipe_call_state_cb(gconstpointer data); static void tklock_datapipe_music_playback_ongoing_cb(gconstpointer data); static void tklock_datapipe_alarm_ui_state_cb(gconstpointer data); static void tklock_datapipe_charger_state_cb(gconstpointer data); static void tklock_datapipe_battery_status_cb(gconstpointer data); static void tklock_datapipe_usb_cable_state_cb(gconstpointer data); static void tklock_datapipe_jack_sense_state_cb(gconstpointer data); static void tklock_datapipe_camera_button_state_cb(gconstpointer const data); static void tklock_datapipe_keypress_event_cb(gconstpointer const data); static void tklock_datapipe_uiexception_type_cb(gconstpointer data); static void tklock_datapipe_audio_route_cb(gconstpointer data); static void tklock_datapipe_tklock_request_cb(gconstpointer data); static void tklock_datapipe_interaction_expected_cb(gconstpointer data); static gpointer tklock_datapipe_submode_filter_cb(gpointer data); static void tklock_datapipe_submode_cb(gconstpointer data); static void tklock_datapipe_lockkey_state_cb(gconstpointer const data); static void tklock_datapipe_heartbeat_event_cb(gconstpointer data); static void tklock_datapipe_keyboard_slide_input_state_cb(gconstpointer const data); static void tklock_datapipe_keyboard_slide_output_state_cb(gconstpointer const data); static void tklock_datapipe_keyboard_available_state_cb(gconstpointer const data); static void tklock_datapipe_light_sensor_poll_request_cb(gconstpointer const data); static void tklock_datapipe_topmost_window_pid_cb(gconstpointer data); static void tklock_datapipe_light_sensor_actual_cb(gconstpointer data); static void tklock_datapipe_lid_sensor_is_working_cb(gconstpointer data); static void tklock_datapipe_lid_sensor_actual_cb(gconstpointer data); static void tklock_datapipe_lid_sensor_filtered_cb(gconstpointer data); static void tklock_datapipe_lens_cover_state_cb(gconstpointer data); static bool tklock_touch_activity_event_p(const struct input_event *ev); static void tklock_datapipe_user_activity_event_cb(gconstpointer data); static void tklock_datapipe_init_done_cb(gconstpointer data); static bool tklock_datapipe_in_tklock_submode(void); static void tklock_datapipe_set_tklock_submode(bool lock); static void tklock_datapipe_set_devicelock_state(devicelock_state_t state); static void tklock_datapipe_rethink_interaction_expected(void); static void tklock_datapipe_update_interaction_expected(bool expected); static void tklock_datapipe_init(void); static void tklock_datapipe_quit(void); // LID_SENSOR static bool tklock_lidsensor_is_enabled (void); static void tklock_lidsensor_init (void); // LID_LIGHT static const char *tklock_lidlight_repr (tklock_lidlight_t state); static tklock_lidlight_t tklock_lidlight_from_lux (int lux); // LID_FILTER static bool tklock_lidfilter_is_enabled (void); static void tklock_lidfilter_set_allow_close (bool allow); static tklock_lidlight_t tklock_lidfilter_map_als_state (void); static void tklock_lidfilter_set_als_state (tklock_lidlight_t state); static gboolean tklock_lidfilter_wait_for_close_cb (gpointer aptr); static bool tklock_lidfilter_get_wait_for_close (void); static void tklock_lidfilter_set_wait_for_close (bool state); static gboolean tklock_lidfilter_wait_for_dark_cb (gpointer aptr); static bool tklock_lidfilter_get_wait_for_dark (void); static void tklock_lidfilter_set_wait_for_dark (bool state); static gboolean tklock_lidfilter_wait_for_light_cb (gpointer aptr); static bool tklock_lidfilter_get_wait_for_light (void); static void tklock_lidfilter_set_wait_for_light (bool state); static void tklock_lidfilter_rethink_als_poll (void); static void tklock_lidfilter_rethink_allow_close (void); static void tklock_lidfilter_rethink_als_state (void); static void tklock_lidfilter_rethink_lid_state (void); // LID_POLICY static void tklock_lidpolicy_rethink (void); // keyboard slide state machine static void tklock_keyboard_slide_opened(void); static void tklock_keyboard_slide_opened_cb(gpointer aptr); static void tklock_keyboard_slide_closed(void); static void tklock_keyboard_slide_rethink(void); // autolock state machine static gboolean tklock_autolock_cb(gpointer aptr); static void tklock_autolock_evaluate(void); static void tklock_autolock_enable(void); static void tklock_autolock_disable(void); static void tklock_autolock_rethink(void); static void tklock_autolock_init(void); static void tklock_autolock_quit(void); // proximity locking state machine static gboolean tklock_proxlock_cb(gpointer aptr); static void tklock_proxlock_resume(void); static void tklock_proxlock_evaluate(void); static void tklock_proxlock_enable(void); static void tklock_proxlock_disable(void); static void tklock_proxlock_rethink(void); // autolock based on device lock changes static void tklock_autolock_on_devlock_block(int duration_ms); static void tklock_autolock_on_devlock_prime(void); static void tklock_autolock_on_devlock_trigger(void); // ui exception handling state machine static uiexception_type_t topmost_active(uiexception_type_t mask); static void tklock_uiexception_sync_to_datapipe(void); static gboolean tklock_uiexception_linger_cb(gpointer aptr); static void tklock_uiexception_begin(uiexception_type_t type, int64_t linger); static void tklock_uiexception_end(uiexception_type_t type, int64_t linger); static void tklock_uiexception_cancel(void); static void tklock_uiexception_finish(void); static bool tklock_uiexception_deny_state_restore(bool force, const char *cause); static void tklock_uiexception_rethink(void); // low power mode ui state machine static void tklock_lpmui_set_state(bool enable); static void tklock_lpmui_reset_history(void); static void tklock_lpmui_update_history(cover_state_t state); static bool tklock_lpmui_probe_from_pocket(void); static bool tklock_lpmui_probe_on_table(void); static bool tklock_lpmui_probe(void); static void tklock_lpmui_rethink(void); static void tklock_lpmui_pre_transition_actions(void); // legacy hw event input enable/disable state machine static void tklock_evctrl_set_state(output_state_t *output, bool enable); static void tklock_evctrl_set_kp_state(bool enable); static void tklock_evctrl_set_ts_state(bool enable); static void tklock_evctrl_set_dt_state(bool enable); static void tklock_evctrl_rethink(void); // legacy hw double tap calibration static void tklock_dtcalib_now(void); static void tklock_dtcalib_from_heartbeat(void); static gboolean tklock_dtcalib_cb(gpointer data); static void tklock_dtcalib_start(void); static void tklock_dtcalib_stop(void); // DYNAMIC_SETTINGS static void tklock_setting_sanitize_lid_open_actions(void); static void tklock_setting_sanitize_lid_close_actions(void); static void tklock_setting_sanitize_kbd_open_trigger(void); static void tklock_setting_sanitize_kbd_open_actions(void); static void tklock_setting_sanitize_kbd_close_trigger(void); static void tklock_setting_sanitize_kbd_close_actions(void); static void tklock_setting_cb(GConfClient *const gcc, const guint id, GConfEntry *const entry, gpointer const data); static void tklock_setting_init(void); static void tklock_setting_quit(void); // sysfs probing static void tklock_sysfs_probe(void); // dbus ipc with systemui static void tklock_ui_send_tklock_signal(void); static void tklock_ui_notify_rethink_wakelock(void); static bool tklock_ui_notify_must_be_delayed(void); static gboolean tklock_ui_notify_end_cb(gpointer data); static gboolean tklock_ui_notify_beg_cb(gpointer data); static void tklock_ui_notify_schdule(void); static gboolean tklock_ui_sync_cb(gpointer aptr); static void tklock_ui_notify_cancel(void); static void tklock_ui_eat_event(void); static void tklock_ui_open(void); static void tklock_ui_close(void); static bool tklock_ui_is_enabled(void); static void tklock_ui_set_enabled(bool enable); static void tklock_ui_get_devicelock_cb(DBusPendingCall *pc, void *aptr); static void tklock_ui_get_devicelock(void); static void tklock_ui_send_lpm_signal(void); static void tklock_ui_enable_lpm(void); static void tklock_ui_disable_lpm(void); static void tklock_ui_show_device_unlock(void);; // dbus ipc static void tklock_dbus_send_display_blanking_policy(DBusMessage *const req); static gboolean tklock_dbus_display_blanking_policy_get_cb(DBusMessage *const msg); static void tklock_dbus_send_keyboard_slide_state(DBusMessage *const req); static gboolean tklock_dbus_keyboard_slide_state_get_req_cb(DBusMessage *const msg); static void tklock_dbus_send_keyboard_available_state(DBusMessage *const req); static gboolean tklock_dbus_keyboard_available_state_get_req_cb(DBusMessage *const msg); static gboolean tklock_dbus_send_tklock_mode(DBusMessage *const method_call); static gboolean tklock_dbus_mode_get_req_cb(DBusMessage *const msg); static tklock_request_t tklock_dbus_sanitize_requested_mode(tklock_request_t state); static gboolean tklock_dbus_mode_change_req_cb(DBusMessage *const msg); static gboolean tklock_dbus_interaction_expected_cb(DBusMessage *const msg); static gboolean tklock_dbus_systemui_callback_cb(DBusMessage *const msg); static gboolean tklock_dbus_devicelock_changed_cb(DBusMessage *const msg); static gboolean tklock_dbus_notification_beg_cb(DBusMessage *const msg); static gboolean tklock_dbus_notification_end_cb(DBusMessage *const msg); static void mce_tklock_init_dbus(void); static void mce_tklock_quit_dbus(void); // NOTIFICATION_SLOTS static void tklock_notif_slot_init(tklock_notif_slot_t *self); static void tklock_notif_slot_free(tklock_notif_slot_t *self); static void tklock_notif_slot_set(tklock_notif_slot_t *self, const char *owner, const char *name, int64_t until, int64_t renew); static bool tklock_notif_slot_is_free(const tklock_notif_slot_t *self); static bool tklock_notif_slot_has_name(const tklock_notif_slot_t *self, const char *name); static bool tklock_notif_slot_validate(tklock_notif_slot_t *self, int64_t now); static bool tklock_notif_slot_renew(tklock_notif_slot_t *self, int64_t now); static bool tklock_notif_slot_has_owner(const tklock_notif_slot_t *self, const char *owner); static gchar *tklock_notif_slot_steal_owner(tklock_notif_slot_t *self); // NOTIFICATION_API static void tklock_notif_init(void); static void tklock_notif_quit(void); static gboolean tklock_notif_autostop_cb(gpointer aptr); static void tklock_notif_cancel_autostop(void); static void tklock_notif_schedule_autostop(gint delay); static void tklock_notif_update_state(void); static void tklock_notif_extend_by_renew(void); static void tklock_notif_vacate_slot(const char *owner, const char *name, int64_t linger); static void tklock_notif_reserve_slot(const char *owner, const char *name, int64_t length, int64_t renew); static void tklock_notif_vacate_slots_from(const char *owner); static size_t tklock_notif_count_slots_from(const char *owner); static gboolean tklock_notif_owner_dropped_cb(DBusMessage *const msg); static void tklock_notif_add_owner_monitor(const char *owner); static void tklock_notif_remove_owner_monitor(const char *owner); static void mce_tklock_begin_notification(const char *owner, const char *name, int64_t length, int64_t renew); static void mce_tklock_end_notification(const char *owner, const char *name, int64_t linger); // "module" load/unload extern gboolean mce_tklock_init(void); extern void mce_tklock_exit(void); extern void mce_tklock_unblank(display_state_t to_state); /* ========================================================================= * * DYNAMIC_SETTINGS * ========================================================================= */ /** Flag: Devicelock is handled in lockscreen */ static gboolean tklock_devicelock_in_lockscreen = MCE_DEFAULT_TK_DEVICELOCK_IN_LOCKSCREEN; static guint tklock_devicelock_in_lockscreen_setting_id = 0; /** Flag: Convert denied tklock removal attempt to: show device unlock view */ static bool tklock_devicelock_want_to_unlock = false; /** Flag: Automatically lock (after ON->DIM->OFF cycle) */ static gboolean tk_autolock_enabled = MCE_DEFAULT_TK_AUTOLOCK_ENABLED; static guint tk_autolock_enabled_setting_id = 0; /** Flag: Grabbing input devices is allowed */ static gboolean tk_input_policy_enabled = MCE_DEFAULT_TK_INPUT_POLICY_ENABLED; static guint tk_input_policy_enabled_setting_id = 0; /** Delay for automatick locking (after ON->DIM->OFF cycle) */ static gint tklock_autolock_delay = MCE_DEFAULT_TK_AUTOLOCK_DELAY; static guint tklock_autolock_delay_setting_id = 0; /** Flag: Proximity sensor can block touch input */ static gboolean proximity_blocks_touch = MCE_DEFAULT_TK_PROXIMITY_BLOCKS_TOUCH; static guint proximity_blocks_touch_setting_id = 0; /** Volume key input policy */ static gint volkey_policy = MCE_DEFAULT_TK_VOLKEY_POLICY; static guint volkey_policy_setting_id = 0; /** Touchscreen gesture (doubletap etc) enable mode */ static gint touchscreen_gesture_enable_mode = MCE_DEFAULT_DOUBLETAP_MODE; static guint touchscreen_gesture_enable_mode_setting_id = 0; /** Lid sensor open actions */ static gint tklock_lid_open_actions = MCE_DEFAULT_TK_LID_OPEN_ACTIONS; static guint tklock_lid_open_actions_setting_id = 0; /** Lid sensor close actions */ static gint tklock_lid_close_actions = MCE_DEFAULT_TK_LID_CLOSE_ACTIONS; static guint tklock_lid_close_actions_setting_id = 0; /** Flag: Is the lid sensor used for display blanking */ static gboolean lid_sensor_enabled = MCE_DEFAULT_TK_LID_SENSOR_ENABLED; static guint lid_sensor_enabled_setting_id = 0; /** When to react to keyboard open */ static gint tklock_kbd_open_trigger = MCE_DEFAULT_TK_KBD_OPEN_TRIGGER; static guint tklock_kbd_open_trigger_setting_id = 0; /** How to react to keyboard open */ static gint tklock_kbd_open_actions = MCE_DEFAULT_TK_KBD_OPEN_ACTIONS; static guint tklock_kbd_open_actions_setting_id = 0; /** When to react to keyboard close */ static gint tklock_kbd_close_trigger = MCE_DEFAULT_TK_KBD_CLOSE_TRIGGER; static guint tklock_kbd_close_trigger_setting_id = 0; /** How to react to keyboard close */ static gint tklock_kbd_close_actions = MCE_DEFAULT_TK_KBD_CLOSE_ACTIONS; static guint tklock_kbd_close_actions_setting_id = 0; /** Flag for: Using ALS is allowed */ static gboolean als_enabled = MCE_DEFAULT_DISPLAY_ALS_ENABLED; static guint als_enabled_setting_id = 0; /** Flag: Use ALS for lid close filtering */ static gboolean filter_lid_with_als = MCE_DEFAULT_TK_FILTER_LID_WITH_ALS; static guint filter_lid_with_als_setting_id = 0; /** Maximum amount of light ALS should report when LID is closed */ static gint filter_lid_als_limit = MCE_DEFAULT_TK_FILTER_LID_ALS_LIMIT; static guint filter_lid_als_limit_setting_id = 0; /** How long to keep display on after incoming call ends [ms] */ static gint exception_length_call_in = MCE_DEFAULT_TK_EXCEPT_LEN_CALL_IN; static guint exception_length_call_in_setting_id = 0; /** How long to keep display on after outgoing call ends [ms] */ static gint exception_length_call_out = MCE_DEFAULT_TK_EXCEPT_LEN_CALL_OUT; static guint exception_length_call_out_setting_id = 0; /** How long to keep display on after alarm is handled [ms] */ static gint exception_length_alarm = MCE_DEFAULT_TK_EXCEPT_LEN_ALARM; static guint exception_length_alarm_setting_id = 0; /** How long to keep display on when usb cable is connected [ms] */ static gint exception_length_usb_connect = MCE_DEFAULT_TK_EXCEPT_LEN_USB_CONNECT; static guint exception_length_usb_connect_setting_id = 0; /** How long to keep display on when usb mode dialog is shown [ms] */ static gint exception_length_usb_dialog = MCE_DEFAULT_TK_EXCEPT_LEN_USB_DIALOG; static guint exception_length_usb_dialog_setting_id = 0; /** How long to keep display on when charging starts [ms] */ static gint exception_length_charger = MCE_DEFAULT_TK_EXCEPT_LEN_CHARGER; static guint exception_length_charger_setting_id = 0; /** How long to keep display on after battery full [ms] */ static gint exception_length_battery = MCE_DEFAULT_TK_EXCEPT_LEN_BATTERY; static guint exception_length_battery_setting_id = 0; /** How long to keep display on when audio jack is inserted [ms] */ static gint exception_length_jack_in = MCE_DEFAULT_TK_EXCEPT_LEN_JACK_IN; static guint exception_length_jack_in_setting_id = 0; /** How long to keep display on when audio jack is removed [ms] */ static gint exception_length_jack_out = MCE_DEFAULT_TK_EXCEPT_LEN_JACK_OUT; static guint exception_length_jack_out_setting_id = 0; /** How long to keep display on when camera button is pressed [ms] */ static gint exception_length_camera = MCE_DEFAULT_TK_EXCEPT_LEN_CAMERA; static guint exception_length_camera_setting_id = 0; /** How long to keep display on when volume button is pressed [ms] */ static gint exception_length_volume = MCE_DEFAULT_TK_EXCEPT_LEN_VOLUME; static guint exception_length_volume_setting_id = 0; /** How long to extend display on when there is user activity [ms] */ static gint exception_length_activity = MCE_DEFAULT_TK_EXCEPT_LEN_ACTIVITY; static guint exception_length_activity_setting_id = 0; /** Flag for: Allow lockscreen animation during unblanking */ static gboolean lockscreen_anim_enabled = MCE_DEFAULT_TK_LOCKSCREEN_ANIM_ENABLED; static guint lockscreen_anim_enabled_setting_id = 0; /** Default delay for delaying proximity uncovered handling [ms] */ static gint tklock_proximity_delay_default = MCE_DEFAULT_TK_PROXIMITY_DELAY_DEFAULT; static guint tklock_proximity_delay_default_setting_id = 0; /** Delay for delaying proximity uncovered handling during calls [ms] */ static gint tklock_proximity_delay_incall = MCE_DEFAULT_TK_PROXIMITY_DELAY_INCALL; static guint tklock_proximity_delay_incall_setting_id = 0; /* ========================================================================= * * probed control file paths * ========================================================================= */ /** SysFS path to touchscreen event disable */ static output_state_t mce_touchscreen_sysfs_disable_output = { .context = "touchscreen_disable", .truncate_file = TRUE, .close_on_exit = TRUE, }; /** SysFS path to touchscreen double-tap gesture control */ static const gchar *mce_touchscreen_gesture_enable_path = NULL; /** SysFS path to touchscreen recalibration control */ static const gchar *mce_touchscreen_calibration_control_path = NULL; /** SysFS path to keypad event disable */ static output_state_t mce_keypad_sysfs_disable_output = { .context = "keypad_disable", .truncate_file = TRUE, .close_on_exit = TRUE, }; /* ========================================================================= * * DATAPIPE VALUES AND TRIGGERS * ========================================================================= */ /** Cached submode_pipe state; assume invalid */ static submode_t submode = MCE_SUBMODE_INVALID; /** Cached PID of process owning the topmost window on UI */ static int topmost_window_pid = -1; /** Cached init_done state; assume unknown */ static tristate_t init_done = TRISTATE_UNKNOWN; /** Proximity state history for triggering low power mode ui */ static ps_history_t tklock_lpmui_hist[8]; /** Current tklock ui state * * Access only via tklock_ui_is_enabled() / tklock_ui_set_enabled(). */ static bool tklock_ui_enabled_pvt = false; /** Current tklock ui state that has been sent to lipstick */ static int tklock_ui_notified = -1; // does not match bool values /** System state; is undefined at bootup, can't assume anything */ static system_state_t system_state = MCE_SYSTEM_STATE_UNDEF; /** Display state; undefined initially, can't assume anything */ static display_state_t display_state_curr = MCE_DISPLAY_UNDEF; /** Next Display state; undefined initially, can't assume anything */ static display_state_t display_state_next = MCE_DISPLAY_UNDEF; /** Call state; assume no active calls */ static call_state_t call_state = CALL_STATE_NONE; /** Actual proximity state; assume not covered */ static cover_state_t proximity_sensor_actual = COVER_UNDEF; /** Effective proximity state; assume not covered */ static cover_state_t proximity_sensor_effective = COVER_UNDEF; /** Lid cover sensor state; assume unkown * * When in covered state, it is assumed that it is not physically * possible to see/interact with the display and thus it should * stay powered off. * * Originally was used to track Nokia N770 slidable cover. Now * it is used also for things like the hammerhead magnetic lid * sensor. */ static cover_state_t lid_sensor_actual = COVER_UNDEF; /** Lid cover policy state; assume unknown */ static cover_state_t lid_sensor_filtered = COVER_UNDEF; /** Change notifications for system_state */ static void tklock_datapipe_system_state_cb(gconstpointer data) { system_state_t prev = system_state; system_state = GPOINTER_TO_INT(data); if( prev == system_state ) goto EXIT; mce_log(LL_DEBUG, "system_state: %s -> %s", system_state_repr(prev), system_state_repr(system_state)); tklock_ui_set_enabled(false); EXIT: return; } /** Device lock state; assume undefined */ static devicelock_state_t devicelock_state = DEVICELOCK_STATE_UNDEFINED; /** Push device lock state value into devicelock_state_pipe datapipe */ static void tklock_datapipe_set_devicelock_state(devicelock_state_t state) { switch( state ) { case DEVICELOCK_STATE_UNLOCKED: case DEVICELOCK_STATE_UNDEFINED: case DEVICELOCK_STATE_LOCKED: break; default: mce_log(LL_WARN, "unknown device lock state=%d; assuming locked", state); state = DEVICELOCK_STATE_LOCKED; break; } if( devicelock_state != state ) { datapipe_exec_full(&devicelock_state_pipe, GINT_TO_POINTER(state)); } } /** Change notifications for devicelock_state */ static void tklock_datapipe_devicelock_state_cb(gconstpointer data) { devicelock_state_t prev = devicelock_state; devicelock_state = GPOINTER_TO_INT(data); if( devicelock_state == prev ) goto EXIT; mce_log(LL_DEVEL, "devicelock_state = %s -> %s", devicelock_state_repr(prev), devicelock_state_repr(devicelock_state)); tklock_uiexception_rethink(); tklock_autolock_rethink(); /* When lipstick is starting up we see device lock * going through undefined -> locked/unlocked change. * We must not trigger autolock due to these initial * device lock transitions */ switch( prev ) { case DEVICELOCK_STATE_UNDEFINED: /* Block autolock for 60 seconds when leaving * undefined state (= lipstick startup) */ tklock_autolock_on_devlock_block(60 * 1000); break; case DEVICELOCK_STATE_LOCKED: /* Unblock autolock earlier if we see transition * away from locked state (=unlocked by user) */ tklock_autolock_on_devlock_block(0); break; default: break; } switch( devicelock_state ) { case DEVICELOCK_STATE_LOCKED: tklock_autolock_on_devlock_trigger(); break; case DEVICELOCK_STATE_UNLOCKED: switch( display_state_next ) { case MCE_DISPLAY_OFF: case MCE_DISPLAY_LPM_OFF: case MCE_DISPLAY_LPM_ON: /* Transitions from undefined -> unlocked state occur * during device bootup / mce restart and should not * cause any actions. */ if( prev == DEVICELOCK_STATE_UNDEFINED ) break; /* Devicelock ui keeps fingerprint scanner active in LPM state * and unlocks device on identify, but omits unlock feedback * and leaves the display state as-is. * * As a workaround, execute unlock feedback from mce. Then * exit from LPM by requesting display power up and removal * of tklock submode. * * While this is mostly relevant to LPM, apply the same logic * also when in actually powered off display states to guard * against timing glitches (getting fingerprint identification * when already decided to exit LPM, etc) and changes in device * lock ui side logic. */ mce_log(LL_WARN, "device got unlocked while display is off; " "assume fingerprint authentication occurred"); datapipe_exec_full(&ngfd_event_request_pipe, "unlock_device"); /* Delay display state / tklock processing until proximity * sensor state is known */ common_on_proximity_schedule(MODULE_NAME, tklock_datapipe_devicelock_state_cb2, 0); break; default: break; } break; default: break; } EXIT: return; } /** Wait for proximity sensor -callback for fingerprint unlock handling * * @param aptr unused */ static void tklock_datapipe_devicelock_state_cb2(gpointer aptr) { (void)aptr; /* Still unlocked ? */ if( devicelock_state == DEVICELOCK_STATE_UNLOCKED ) { if( proximity_sensor_actual != COVER_OPEN ) { mce_log(LL_WARN, "unblank skipped due to proximity sensor"); } else { mce_datapipe_request_display_state(MCE_DISPLAY_ON); mce_datapipe_request_tklock(TKLOCK_REQUEST_OFF); } } } /** Resumed from suspend notification */ static void tklock_datapipe_resume_detected_event_cb(gconstpointer data) { (void) data; /* Re-evaluate proximity locking after resuming from * suspend. */ tklock_proxlock_resume(); } /** devicelock dbus name is reserved; assume unknown */ static service_state_t devicelock_service_state = SERVICE_STATE_UNDEF; /** Change notifications for devicelock_service_state */ static void tklock_datapipe_devicelock_service_state_cb(gconstpointer data) { service_state_t prev = devicelock_service_state; devicelock_service_state = GPOINTER_TO_INT(data); if( devicelock_service_state == prev ) goto EXIT; mce_log(LL_DEBUG, "devicelock_service_state = %s -> %s", service_state_repr(prev), service_state_repr(devicelock_service_state)); if( devicelock_service_state == SERVICE_STATE_RUNNING ) { /* query initial device lock state on devicelock/mce startup */ tklock_ui_get_devicelock(); } else { /* if device lock service is not running, the device lock * state is undefined */ tklock_datapipe_set_devicelock_state(DEVICELOCK_STATE_UNDEFINED); } EXIT: return; } /** Lipstick dbus name is reserved; assume false */ static service_state_t lipstick_service_state = SERVICE_STATE_UNDEF; /** Change notifications for lipstick_service_state */ static void tklock_datapipe_lipstick_service_state_cb(gconstpointer data) { service_state_t prev = lipstick_service_state; lipstick_service_state = GPOINTER_TO_INT(data); if( lipstick_service_state == prev ) goto EXIT; mce_log(LL_DEBUG, "lipstick_service_state = %s -> %s", service_state_repr(prev), service_state_repr(lipstick_service_state)); bool enable_tklock = false; /* Tklock is applicable only when lipstick is running */ if( lipstick_service_state == SERVICE_STATE_RUNNING ) { /* STOPPED -> RUNNING: Implies lipstick start / restart. * In this case lockscreen status is decided by lipstick. * We achieve tklock state synchronization by making a * lockscreen deactivation request - which lipstick can * then choose to honor or override. * * UNDEF -> RUNNING: Implies a mce restart while lipstick * is running. What we would like to happen is that * things stay exactly as they were. However there is * no way to recover lockscreen state from lipstick. * So in order to err on the safer side, we activate * lockscreen to get tklock state in sync again. */ if( prev == SERVICE_STATE_UNDEF ) enable_tklock = true; } // force tklock ipc tklock_ui_notified = -1; tklock_ui_set_enabled(enable_tklock); EXIT: return; } /** Update mode is active; assume false */ static bool osupdate_running = false; /** Change notifications for osupdate_running */ static void tklock_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); if( osupdate_running ) { /* undo tklock when update mode starts */ mce_datapipe_request_tklock(TKLOCK_REQUEST_OFF); } EXIT: return; } /** Device is shutting down; assume false */ static bool shutting_down = false; /** Change notifications for shutting_down */ static void tklock_datapipe_shutting_down_cb(gconstpointer data) { bool prev = shutting_down; shutting_down = GPOINTER_TO_INT(data); if( shutting_down == prev ) goto EXIT; mce_log(LL_DEBUG, "shutting_down = %d -> %d", prev, shutting_down); tklock_evctrl_rethink(); EXIT: return; } /** Autorelock trigger: assume disabled */ static autorelock_t autorelock_trigger = AUTORELOCK_NO_TRIGGERS; /** Change notifications for display_state_curr */ static void tklock_datapipe_display_state_curr_cb(gconstpointer data) { display_state_t prev = display_state_curr; display_state_curr = GPOINTER_TO_INT(data); if( display_state_curr == prev ) goto EXIT; mce_log(LL_DEBUG, "display_state_curr = %s -> %s", display_state_repr(prev), display_state_repr(display_state_curr)); tklock_datapipe_rethink_interaction_expected(); tklock_lidfilter_rethink_allow_close(); /* Disable "wakeup with fake policy" hack * when any stable display state is reached */ if( display_state_curr != MCE_DISPLAY_POWER_UP && display_state_curr != MCE_DISPLAY_POWER_DOWN ) tklock_uiexception_end(UIEXCEPTION_TYPE_NOANIM, 0); if( display_state_curr == MCE_DISPLAY_DIM ) tklock_ui_eat_event(); tklock_uiexception_rethink(); tklock_autolock_rethink(); tklock_proxlock_rethink(); tklock_evctrl_rethink(); tklock_ui_notify_schdule(); EXIT: return; } /** Pre-change notifications for display_state_curr */ static void tklock_datapipe_display_state_next_cb(gconstpointer data) { display_state_next = GPOINTER_TO_INT(data); mce_log(LL_DEBUG, "display_state_next = %s -> %s", display_state_repr(display_state_curr), display_state_repr(display_state_next)); if( display_state_next == display_state_curr ) goto EXIT; /* Cancel autorelock on display off */ switch( display_state_next ) { case MCE_DISPLAY_ON: case MCE_DISPLAY_DIM: /* display states that use normal ui */ break; default: /* display powered off, showing lpm, etc */ if( autorelock_trigger != AUTORELOCK_NO_TRIGGERS ) { mce_log(LL_DEBUG, "autorelock canceled: display off"); autorelock_trigger = AUTORELOCK_NO_TRIGGERS; } break; } tklock_autolock_on_devlock_prime(); tklock_autolock_rethink(); tklock_proxlock_rethink(); tklock_lpmui_pre_transition_actions(); tklock_ui_notify_schdule(); EXIT: return; } /** Timer id for delayed proximity uncovering */ static guint tklock_datapipe_proximity_uncover_id = 0; /** Re-evaluate proximity sensor debugging led pattern state */ static void tklock_datapipe_proximity_eval_led(void) { typedef enum { PROXIMITY_LED_STATE_UNDEFINED = 0, PROXIMITY_LED_STATE_COVERED = 1, PROXIMITY_LED_STATE_UNCOVERING = 2, PROXIMITY_LED_STATE_UNCOVERED = 3, } proximity_led_state_t; static proximity_led_state_t prev = PROXIMITY_LED_STATE_UNDEFINED; /* Evaluate what led pattern should be active */ proximity_led_state_t curr = PROXIMITY_LED_STATE_UNDEFINED; if( proximity_sensor_effective == COVER_OPEN ) curr = PROXIMITY_LED_STATE_UNCOVERED; else if( proximity_sensor_actual == COVER_OPEN ) curr = PROXIMITY_LED_STATE_UNCOVERING; else if( proximity_sensor_actual == COVER_CLOSED ) curr = PROXIMITY_LED_STATE_COVERED; if( prev == curr ) goto EXIT; /* Activate new pattern 1st, then deactivate old pattern * to avoid transition via no active pattern. */ switch( curr ) { case PROXIMITY_LED_STATE_UNCOVERED: datapipe_exec_full(&led_pattern_activate_pipe, MCE_LED_PATTERN_PROXIMITY_UNCOVERED); break; case PROXIMITY_LED_STATE_UNCOVERING: datapipe_exec_full(&led_pattern_activate_pipe, MCE_LED_PATTERN_PROXIMITY_UNCOVERING); break; case PROXIMITY_LED_STATE_COVERED: datapipe_exec_full(&led_pattern_activate_pipe, MCE_LED_PATTERN_PROXIMITY_COVERED); break; default: break; } switch( prev ) { case PROXIMITY_LED_STATE_UNCOVERED: datapipe_exec_full(&led_pattern_deactivate_pipe, MCE_LED_PATTERN_PROXIMITY_UNCOVERED); break; case PROXIMITY_LED_STATE_UNCOVERING: datapipe_exec_full(&led_pattern_deactivate_pipe, MCE_LED_PATTERN_PROXIMITY_UNCOVERING); break; case PROXIMITY_LED_STATE_COVERED: datapipe_exec_full(&led_pattern_deactivate_pipe, MCE_LED_PATTERN_PROXIMITY_COVERED); break; default: break; } prev = curr; EXIT: return; } /** Set effective proximity state from current sensor state */ static void tklock_datapipe_proximity_update(void) { if( proximity_sensor_effective == proximity_sensor_actual ) goto EXIT; mce_log(LL_DEBUG, "proximity_sensor_effective = %s -> %s", proximity_state_repr(proximity_sensor_effective), proximity_state_repr(proximity_sensor_actual)); proximity_sensor_effective = proximity_sensor_actual; datapipe_exec_full(&proximity_sensor_effective_pipe, GINT_TO_POINTER(proximity_sensor_effective)); tklock_datapipe_proximity_eval_led(); tklock_uiexception_rethink(); tklock_proxlock_rethink(); tklock_evctrl_rethink(); /* consider moving to lpm ui */ tklock_lpmui_rethink(); EXIT: return; } /** Timer callback for handling delayed proximity uncover */ static gboolean tklock_datapipe_proximity_uncover_cb(gpointer data) { (void)data; if( !tklock_datapipe_proximity_uncover_id ) goto EXIT; tklock_datapipe_proximity_uncover_id = 0; tklock_datapipe_proximity_update(); wakelock_unlock("mce_proximity_stm"); EXIT: return FALSE; } /** Cancel delayed proximity uncovering */ static void tklock_datapipe_proximity_uncover_cancel(void) { if( tklock_datapipe_proximity_uncover_id ) { g_source_remove(tklock_datapipe_proximity_uncover_id), tklock_datapipe_proximity_uncover_id = 0; wakelock_unlock("mce_proximity_stm"); } } /** Schedule delayed proximity uncovering */ static void tklock_datapipe_proximity_uncover_schedule(void) { if( tklock_datapipe_proximity_uncover_id ) g_source_remove(tklock_datapipe_proximity_uncover_id); else wakelock_lock("mce_proximity_stm", -1); int delay = tklock_proximity_delay_default; if( call_state == CALL_STATE_ACTIVE ) delay = tklock_proximity_delay_incall; if( delay < MCE_MINIMUM_TK_PROXIMITY_DELAY ) delay = MCE_MINIMUM_TK_PROXIMITY_DELAY; else if( delay > MCE_MAXIMUM_TK_PROXIMITY_DELAY ) delay = MCE_MAXIMUM_TK_PROXIMITY_DELAY; tklock_datapipe_proximity_uncover_id = g_timeout_add(delay, tklock_datapipe_proximity_uncover_cb, 0); } /** Change notifications for proximity_sensor_actual */ static void tklock_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)); tklock_datapipe_proximity_eval_led(); /* update lpm ui proximity history using raw data */ tklock_lpmui_update_history(proximity_sensor_actual); if( proximity_sensor_actual == COVER_OPEN ) { tklock_datapipe_proximity_uncover_schedule(); } else { tklock_datapipe_proximity_uncover_cancel(); tklock_datapipe_proximity_update(); } EXIT: return; } /** Change notifications for call_state */ static void tklock_datapipe_call_state_cb(gconstpointer data) { /* Default to using shorter outgoing call linger time */ static bool incoming = false; call_state_t prev = call_state; call_state = GPOINTER_TO_INT(data); if( call_state == CALL_STATE_INVALID ) call_state = CALL_STATE_NONE; if( call_state == prev ) goto EXIT; mce_log(LL_DEBUG, "call_state = %s -> %s", call_state_repr(prev), call_state_repr(call_state)); switch( call_state ) { case CALL_STATE_RINGING: /* Switch to using longer incoming call linger time */ incoming = true; /* Fall through */ case CALL_STATE_ACTIVE: tklock_uiexception_begin(UIEXCEPTION_TYPE_CALL, 0); break; default: tklock_uiexception_end(UIEXCEPTION_TYPE_CALL, incoming ? exception_length_call_in : exception_length_call_out); /* Restore linger time to default again */ incoming = false; break; } // display on/off policy tklock_uiexception_rethink(); // volume keys during call tklock_evctrl_rethink(); EXIT: return; } /** Music playback state; assume not playing */ static bool music_playback_ongoing = false; /** Change notifications for music_playback_ongoing */ static void tklock_datapipe_music_playback_ongoing_cb(gconstpointer data) { bool prev = music_playback_ongoing; music_playback_ongoing = GPOINTER_TO_INT(data); if( music_playback_ongoing == prev ) goto EXIT; mce_log(LL_DEBUG, "music_playback_ongoing = %d -> %d", prev, music_playback_ongoing); // volume keys during playback tklock_evctrl_rethink(); EXIT: return; } /** Alarm state; assume no active alarms */ static alarm_ui_state_t alarm_ui_state = MCE_ALARM_UI_OFF_INT32; /** Change notifications for alarm_ui_state */ static void tklock_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 == MCE_ALARM_UI_INVALID_INT32 ) alarm_ui_state = MCE_ALARM_UI_OFF_INT32; 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)); switch( alarm_ui_state ) { case MCE_ALARM_UI_RINGING_INT32: case MCE_ALARM_UI_VISIBLE_INT32: tklock_uiexception_begin(UIEXCEPTION_TYPE_ALARM, 0); break; default: tklock_uiexception_end(UIEXCEPTION_TYPE_ALARM, exception_length_alarm); break; } tklock_uiexception_rethink(); EXIT: return; } /** Charger state; assume not charging */ static charger_state_t charger_state = CHARGER_STATE_UNDEF; /** Change notifications for charger_state */ static void tklock_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)); /* No exception on mce startup */ if( prev == CHARGER_STATE_UNDEF ) goto EXIT; /* Notification expected when charging starts */ if( charger_state == CHARGER_STATE_ON ) mce_tklock_begin_notification(0, "mce_charger_state", exception_length_charger, -1); EXIT: return; } /** Battery status; not known initially, can't assume anything */ static battery_status_t battery_status = BATTERY_STATUS_UNDEF; /** Change notifications for battery_status */ static void tklock_datapipe_battery_status_cb(gconstpointer data) { battery_status_t prev = battery_status; battery_status = GPOINTER_TO_INT(data); if( battery_status == prev ) goto EXIT; mce_log(LL_DEBUG, "battery_status = %s -> %s", battery_status_repr(prev), battery_status_repr(battery_status)); if( battery_status == BATTERY_STATUS_FULL ) { mce_tklock_begin_notification(0, "mce_battery_full", exception_length_battery, -1); } EXIT: return; } /** USB cable status; assume disconnected */ static usb_cable_state_t usb_cable_state = USB_CABLE_UNDEF; /** Change notifications for usb_cable_state */ static void tklock_datapipe_usb_cable_state_cb(gconstpointer data) { usb_cable_state_t prev = usb_cable_state; usb_cable_state = GPOINTER_TO_INT(data); if( prev == usb_cable_state ) goto EXIT; mce_log(LL_DEBUG, "usb_cable_state = %s -> %s", usb_cable_state_repr(prev), usb_cable_state_repr(usb_cable_state)); /* No exception on mce startup */ if( prev == USB_CABLE_UNDEF ) goto EXIT; switch( usb_cable_state ) { case USB_CABLE_DISCONNECTED: mce_tklock_end_notification(0, "mce_usb_connect", 0); mce_tklock_end_notification(0, "mce_usb_dialog", 0); break; case USB_CABLE_CONNECTED: mce_tklock_begin_notification(0, "mce_usb_connect", exception_length_usb_connect, -1); break; case USB_CABLE_ASK_USER: mce_tklock_begin_notification(0, "mce_usb_dialog", exception_length_usb_dialog, -1); break; default: goto EXIT; } EXIT: return; } /** Audio jack state; assume not known yet */ static cover_state_t jack_sense_state = COVER_UNDEF; /** Change notifications for jack_sense_state */ static void tklock_datapipe_jack_sense_state_cb(gconstpointer data) { cover_state_t prev = jack_sense_state; jack_sense_state = GPOINTER_TO_INT(data); if( prev == jack_sense_state ) goto EXIT; mce_log(LL_DEBUG, "jack_sense_state = %s -> %s", cover_state_repr(prev), cover_state_repr(jack_sense_state)); if( prev == COVER_UNDEF ) goto EXIT; int64_t length = -1; switch( jack_sense_state ) { case COVER_CLOSED: length = exception_length_jack_in; break; case COVER_OPEN: length = exception_length_jack_out; break; default: break; } mce_tklock_begin_notification(0, "mce_jack_sense", length, -1); EXIT: return; } /** Change notifications for camera_button */ static void tklock_datapipe_camera_button_state_cb(gconstpointer const data) { /* TODO: This might make no sense, need to check on HW that has * dedicated camera button ... */ (void)data; mce_tklock_begin_notification(0, "mce_camera_button", exception_length_camera, -1); } /** Change notifications for keypress */ static void tklock_datapipe_keypress_event_cb(gconstpointer const data) { const struct input_event *const*evp; const struct input_event *ev; if( !(evp = data) ) goto EXIT; if( !(ev = *evp) ) goto EXIT; // ignore non-key events if( ev->type != EV_KEY ) goto EXIT; // ignore key up events if( ev->value == 0 ) goto EXIT; switch( ev->code ) { case KEY_POWER: // power key events are handled in powerkey.c break; case KEY_CAMERA: mce_log(LL_DEBUG, "camera key"); mce_tklock_begin_notification(0, "mce_camera_key", exception_length_camera, -1); break; case KEY_VOLUMEDOWN: case KEY_VOLUMEUP: if( datapipe_get_gint(keypad_grab_wanted_pipe) ) { mce_log(LL_DEVEL, "volume key ignored"); break; } mce_log(LL_DEBUG, "volume key"); mce_tklock_begin_notification(0, "mce_volume_key", exception_length_volume, -1); break; default: break; } EXIT: return; } /** UI exception state; initialized to none */ static uiexception_type_t uiexception_type = UIEXCEPTION_TYPE_NONE; /** Change notifications for uiexception_type */ static void tklock_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_CRUCIAL, "uiexception_type = %s -> %s", uiexception_type_repr(prev), uiexception_type_repr(uiexception_type)); /* Cancel autorelock if there is a call or alarm */ if( (uiexception_type & (UIEXCEPTION_TYPE_CALL | UIEXCEPTION_TYPE_ALARM)) && autorelock_trigger != AUTORELOCK_NO_TRIGGERS ) { mce_log(LL_DEBUG, "autorelock canceled: handling call/alarm"); autorelock_trigger = AUTORELOCK_NO_TRIGGERS; } /* Forget lpm ui triggering history * whenever exception state changes */ tklock_lpmui_reset_history(); tklock_autolock_rethink(); tklock_proxlock_rethink(); /* Broadcast blanking policy change */ tklock_dbus_send_display_blanking_policy(0); EXIT: return; } /** Audio routing state; assume handset */ static audio_route_t audio_route = AUDIO_ROUTE_HANDSET; /** Change notifications for audio_route */ static void tklock_datapipe_audio_route_cb(gconstpointer data) { audio_route_t prev = audio_route; audio_route = GPOINTER_TO_INT(data); if( audio_route == AUDIO_ROUTE_UNDEF ) audio_route = AUDIO_ROUTE_HANDSET; if( audio_route == prev ) goto EXIT; mce_log(LL_DEBUG, "audio_route = %d -> %d", prev, audio_route); tklock_uiexception_rethink(); EXIT: return; } /** Change notifications for tklock_request_pipe * * Handles tklock requests from outside this module */ static void tklock_datapipe_tklock_request_cb(gconstpointer data) { tklock_request_t tklock_request = GPOINTER_TO_INT(data); mce_log(LL_DEBUG, "tklock_request = %s", tklock_request_repr(tklock_request)); bool enable = tklock_ui_is_enabled(); switch( tklock_request ) { case TKLOCK_REQUEST_UNDEF: case TKLOCK_REQUEST_OFF: case TKLOCK_REQUEST_OFF_DELAYED: enable = false; break; default: case TKLOCK_REQUEST_OFF_PROXIMITY: case TKLOCK_REQUEST_ON: case TKLOCK_REQUEST_ON_DIMMED: case TKLOCK_REQUEST_ON_PROXIMITY: case TKLOCK_REQUEST_ON_DELAYED: enable = true; break; case TKLOCK_REQUEST_TOGGLE: enable = !enable; } tklock_ui_set_enabled(enable); } /** Interaction expected; assume false */ static bool interaction_expected = false; /** Interaction expected; unfiltered info from compositor */ static bool interaction_expected_raw = false; /** Change notifications for interaction_expected_pipe */ static void tklock_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); /* All changes must be ignored when handling exceptional things * like calls and alarms that are shown on top of lockscreen ui. */ if( uiexception_type & (UIEXCEPTION_TYPE_CALL | UIEXCEPTION_TYPE_ALARM) ) goto EXIT; /* Edge triggered action: When interaction becomes expected * while lockscreen is still active (e.g. display has been * unblanked to show notification on the lockscreen and * user has swiped from plain lockscreen view to device unlock * code entry view) the display state restore should be disabled. */ if( display_state_next == MCE_DISPLAY_ON && tklock_ui_is_enabled() && interaction_expected ) { tklock_uiexception_deny_state_restore(true, "interaction expected"); } EXIT: return; } /** Re-evaluate effective interaction_expected value * * The notifications from compositor side do not always make * sense from mce point of view. * * This function: * - Normalizes the interaction_expected value by filtering out * obviously impossible situations such as having interacation * expected while display is powered off. * - Should be called whenever state variables used in the * filtering have changed. */ static void tklock_datapipe_rethink_interaction_expected(void) { bool use = interaction_expected_raw; switch( display_state_curr ) { case MCE_DISPLAY_ON: case MCE_DISPLAY_DIM: /* Display is in state that allows interaction */ if( submode & MCE_SUBMODE_TKLOCK ) { /* Lockscreen active * -> use reported state */ } else if( topmost_window_pid == -1 ) { /* Home screen active * -> use reported state */ } else { /* Application active * -> ignore reported state */ use = true; } break; default: /* Display is not in state allowing interaction * -> ignore reported state */ use = false; break; } if( interaction_expected != use ) datapipe_exec_full(&interaction_expected_pipe, GINT_TO_POINTER(use)); } /** Update raw interaction expected state * * Updates cached raw value and re-calculates effective value. * * @param expected state as reported by compositor */ static void tklock_datapipe_update_interaction_expected(bool expected) { if( interaction_expected_raw == expected ) goto EXIT; mce_log(LL_DEBUG, "interaction_expected_raw: %d -> %d", interaction_expected_raw, expected); interaction_expected_raw = expected; tklock_datapipe_rethink_interaction_expected(); EXIT: return; } /** Filter tklock submode changes * * All tklock submode changes are subjected to policy implemented * at tklock_ui_xxx() functions. * * Basically this ensures tklock_datapipe_submode_cb() will never * see submode values where tklock would not agree with policy. */ static gpointer tklock_datapipe_submode_filter_cb(gpointer data) { submode_t input = GPOINTER_TO_INT(data); submode_t output = input; tklock_ui_set_enabled(input & MCE_SUBMODE_TKLOCK); if( tklock_ui_is_enabled() ) output |= MCE_SUBMODE_TKLOCK; else output &= ~MCE_SUBMODE_TKLOCK; if( input != output ) mce_log(LL_DEBUG, "submode filter: %s", submode_change_repr(input, output)); return GINT_TO_POINTER(output); } /** Change notifications for submode */ static void tklock_datapipe_submode_cb(gconstpointer data) { submode_t prev = submode; submode = GPOINTER_TO_INT(data); if( submode == prev ) goto EXIT; /* Note: Due to filtering at tklock_datapipe_submode_filter_cb() * the submode value seen here is always in sync with policy * implemented at tklock_ui_xxx() functions. */ mce_log(LL_DEBUG, "submode = %s", submode_change_repr(prev, submode)); // out of sync tklock state blocks state restore tklock_uiexception_rethink(); // block tklock removal while autolock rules apply tklock_autolock_rethink(); tklock_proxlock_rethink(); tklock_evctrl_rethink(); // skip the rest if tklock did not change if( !((prev ^ submode) & MCE_SUBMODE_TKLOCK) ) goto EXIT; tklock_datapipe_rethink_interaction_expected(); if( submode & MCE_SUBMODE_TKLOCK ) { // tklock added } else { // tklock removed switch( display_state_next ) { case MCE_DISPLAY_LPM_ON: case MCE_DISPLAY_LPM_OFF: /* We're currently in or transitioning to lpm display state * and tklock just got removed. Normally this should not * happen, so emit error message to journal. */ mce_log(LL_ERR, "tklock submode was removed in lpm state"); /* Nevertheless, removal of tklock means there is something * happening at the ui side - and probably the best course of * action is to cancel lpm state by turning on the display. */ mce_datapipe_request_display_state(MCE_DISPLAY_ON); break; default: case MCE_DISPLAY_UNDEF: case MCE_DISPLAY_OFF: case MCE_DISPLAY_DIM: case MCE_DISPLAY_ON: // nop break; } } EXIT: return; } /** Query the touchscreen/keypad lock status * * @return TRUE if the touchscreen/keypad lock is enabled, * FALSE if the touchscreen/keypad lock is disabled */ static bool tklock_datapipe_in_tklock_submode(void) { return (submode & MCE_SUBMODE_TKLOCK) != 0; } static void tklock_datapipe_set_tklock_submode(bool lock) { /* This function should be called only via: * * tklock_ui_set_enabled() * tklock_ui_sync_cb() * tklock_datapipe_set_tklock_submode() */ mce_log(LL_DEBUG, "tklock submode request: %s", lock ? "LOCK" : "UNLOCK"); if( lock ) mce_add_submode_int32(MCE_SUBMODE_TKLOCK); else mce_rem_submode_int32(MCE_SUBMODE_TKLOCK); } /** Change notifications for lockkey_state_pipe */ static void tklock_datapipe_lockkey_state_cb(gconstpointer const data) { /* TODO: IIRC lock key is N900 hw feature, I have not had a chance * to test if this actually works ... */ key_state_t key_state = GPOINTER_TO_INT(data); mce_log(LL_DEBUG, "lockkey: %s", key_state_repr(key_state)); /* Ignore release events */ if( key_state != KEY_STATE_PRESSED ) goto EXIT; /* Try to give it the same treatment as power key would get. * Copy pasted from generic_powerkey_handler() @ powerkey.c */ switch( display_state_next ) { case MCE_DISPLAY_ON: case MCE_DISPLAY_DIM: case MCE_DISPLAY_POWER_UP: mce_log(LL_DEBUG, "display -> off + lock"); /* Do the locking before turning display off. * * The tklock requests get ignored in act dead * etc, so we can just blindly request it. */ mce_datapipe_request_tklock(TKLOCK_REQUEST_ON); mce_datapipe_request_display_state(MCE_DISPLAY_OFF); break; default: case MCE_DISPLAY_UNDEF: case MCE_DISPLAY_OFF: case MCE_DISPLAY_LPM_OFF: case MCE_DISPLAY_LPM_ON: case MCE_DISPLAY_POWER_DOWN: mce_log(LL_DEBUG, "display -> on"); mce_datapipe_request_display_state(MCE_DISPLAY_ON); break; } EXIT: return; } /** Change notifications for heartbeat_event_pipe */ static void tklock_datapipe_heartbeat_event_cb(gconstpointer data) { (void)data; mce_log(LL_DEBUG, "heartbeat"); tklock_dtcalib_from_heartbeat(); } /** Keypad slide input state; assume closed */ static cover_state_t keyboard_slide_input_state = COVER_CLOSED; /** Change notifications from keyboard_slide_state_pipe */ static void tklock_datapipe_keyboard_slide_input_state_cb(gconstpointer const data) { cover_state_t prev = keyboard_slide_input_state; keyboard_slide_input_state = GPOINTER_TO_INT(data); if( keyboard_slide_input_state == COVER_UNDEF ) keyboard_slide_input_state = COVER_CLOSED; if( keyboard_slide_input_state == prev ) goto EXIT; mce_log(LL_DEVEL, "keyboard_slide_input_state = %s -> %s", cover_state_repr(prev), cover_state_repr(keyboard_slide_input_state)); tklock_keyboard_slide_rethink(); EXIT: return; } /** Keypad slide output state; assume unknown */ static cover_state_t keyboard_slide_output_state = COVER_UNDEF; static void tklock_datapipe_keyboard_slide_output_state_cb(gconstpointer const data) { cover_state_t prev = keyboard_slide_output_state; keyboard_slide_output_state = GPOINTER_TO_INT(data); if( keyboard_slide_output_state == prev ) goto EXIT; mce_log(LL_DEVEL, "keyboard_slide_output_state = %s -> %s", cover_state_repr(prev), cover_state_repr(keyboard_slide_output_state)); tklock_dbus_send_keyboard_slide_state(0); EXIT: return; } /** Keypad available output state; assume unknown */ static cover_state_t keyboard_available_state = COVER_UNDEF; static void tklock_datapipe_keyboard_available_state_cb(gconstpointer const data) { cover_state_t prev = keyboard_available_state; keyboard_available_state = GPOINTER_TO_INT(data); if( keyboard_available_state == prev ) goto EXIT; mce_log(LL_DEBUG, "keyboard_available_state = %s -> %s", cover_state_repr(prev), cover_state_repr(keyboard_available_state)); tklock_dbus_send_keyboard_available_state(0); EXIT: return; } /** Cached als poll state; tracked via tklock_datapipe_light_sensor_poll_request_cb() */ static gboolean light_sensor_polling = FALSE; /** Ambient Light Sensor filter for temporary sensor enable * * @param data Polling enabled/disabled bool (as void pointer) */ static void tklock_datapipe_light_sensor_poll_request_cb(gconstpointer const data) { gboolean prev = light_sensor_polling; light_sensor_polling = GPOINTER_TO_INT(data) ? TRUE : FALSE; mce_log(LL_DEBUG, "light_sensor_polling: %s -> %s", prev ? "true" : "false", light_sensor_polling ? "true" : "false"); /* Check without comparing to previous state. The poll * request can be denied by datapipe filter at the als * plugin - in which case we see a false->false transition * here at datapipe output trigger callback. */ tklock_lidfilter_rethink_als_poll(); } /** Change notifications for topmost_window_pid_pipe */ static void tklock_datapipe_topmost_window_pid_cb(gconstpointer data) { int prev = topmost_window_pid; topmost_window_pid = GPOINTER_TO_INT(data); if( prev == topmost_window_pid ) goto EXIT; mce_log(LL_DEBUG, "topmost_window_pid: %d -> %d", prev, topmost_window_pid); tklock_datapipe_rethink_interaction_expected(); EXIT: return; } /** Raw ambient light sensor state; assume unknown */ static int light_sensor_actual = -1; /** Change notifications from light_sensor_actual_pipe */ static void tklock_datapipe_light_sensor_actual_cb(gconstpointer data) { cover_state_t prev = light_sensor_actual; light_sensor_actual = GPOINTER_TO_INT(data); if( light_sensor_actual == prev ) goto EXIT; mce_log(LL_DEBUG, "light_sensor_actual = %d -> %d", prev, light_sensor_actual); tklock_lidfilter_rethink_als_state(); EXIT: return; } /** Assume lid sensor is broken until we have seen closed->open transition * * If the lid sensor is used for display blanking, a faulty sensor can * cause a lot of problems. * * To avoid this mce tracks persistently whether the sensor on the device * has been seen to function on previous mce invocations. * * This cached state must be recovered before the datapipe callbacks * that depend on it are hooked up. */ static bool tklock_lid_sensor_is_working = false; /** Path to the flag file for persistent tklock_lid_sensor_is_working */ #define LID_SENSOR_IS_WORKING_FLAG_FILE "/var/lib/mce/lid_sensor_is_working" /** Keep flag file in sync with lid_sensor_is_working_pipe status */ static void tklock_datapipe_lid_sensor_is_working_cb(gconstpointer data) { bool prev = tklock_lid_sensor_is_working; tklock_lid_sensor_is_working = GPOINTER_TO_INT(data); if( tklock_lid_sensor_is_working == prev ) goto EXIT; mce_log(LL_DEVEL, "lid_sensor_is_working = %s -> %s", prev ? "true" : "false", tklock_lid_sensor_is_working ? "true" : "false"); if( tklock_lid_sensor_is_working ) { /* Create flag file */ int fd = open(LID_SENSOR_IS_WORKING_FLAG_FILE, O_WRONLY|O_CREAT, 0644); if( fd == -1 ) mce_log(LL_WARN, "%s: could not create flag file: %m", LID_SENSOR_IS_WORKING_FLAG_FILE); else close(fd); tklock_lidpolicy_rethink(); } else { /* Remove flag file */ if( unlink(LID_SENSOR_IS_WORKING_FLAG_FILE) == -1 && errno != ENOENT ) mce_log(LL_WARN, "%s: could not remove flag file: %m", LID_SENSOR_IS_WORKING_FLAG_FILE); /* Invalidate sensor data */ datapipe_exec_full(&lid_sensor_actual_pipe, GINT_TO_POINTER(COVER_UNDEF)); } EXIT: return; } /** Change notifications from lid_sensor_actual_pipe */ static void tklock_datapipe_lid_sensor_actual_cb(gconstpointer data) { cover_state_t prev = lid_sensor_actual; lid_sensor_actual = GPOINTER_TO_INT(data); if( lid_sensor_actual == prev ) goto EXIT; if( prev == COVER_CLOSED && lid_sensor_actual == COVER_OPEN ) { /* We have seen the sensor flip from closed to open position, * so we can stop assuming it stays closed forever */ datapipe_exec_full(&lid_sensor_is_working_pipe, GINT_TO_POINTER(true)); } mce_log(LL_DEVEL, "lid_sensor_actual = %s -> %s", cover_state_repr(prev), cover_state_repr(lid_sensor_actual)); tklock_lidfilter_rethink_lid_state(); EXIT: return; } /** Change notifications from lid_sensor_filtered_pipe */ static void tklock_datapipe_lid_sensor_filtered_cb(gconstpointer data) { cover_state_t prev = lid_sensor_filtered; lid_sensor_filtered = GPOINTER_TO_INT(data); if( lid_sensor_filtered == prev ) goto EXIT; mce_log(LL_DEVEL, "lid_sensor_filtered = %s -> %s", cover_state_repr(prev), cover_state_repr(lid_sensor_filtered)); /* TODO: On devices that have means to detect physically covered * display, it might be desirable to also power off: * - proximity sensor * - notification led * - double tap detection * * Note: Logic for volume key control exists, but is not used atm */ /* Re-evaluate need for touch blocking */ tklock_evctrl_rethink(); EXIT: return; } /** Camera lens cover state; assume closed */ static cover_state_t lens_cover_state = COVER_CLOSED; /** Change notifications from lens_cover_state_pipe */ static void tklock_datapipe_lens_cover_state_cb(gconstpointer data) { cover_state_t prev = lens_cover_state; lens_cover_state = GPOINTER_TO_INT(data); if( lens_cover_state == COVER_UNDEF ) lens_cover_state = COVER_CLOSED; if( lens_cover_state == prev ) goto EXIT; mce_log(LL_DEBUG, "lens_cover_state = %s -> %s", cover_state_repr(prev), cover_state_repr(lens_cover_state)); // TODO: COVER_OPEN -> display on, unlock, reason=AUTORELOCK_KBD_SLIDE // TODO: COVER_CLOSE -> display off, lock if reason==AUTORELOCK_KBD_SLIDE EXIT: return; } /** Check if event relates to ongoing user activity on screen * * Detect touch screen events that signify finger on screen * situation. * * To make things work in SDK do mouse click detection too. */ static bool tklock_touch_activity_event_p(const struct input_event *ev) { bool activity = false; switch( ev->type ) { case EV_KEY: switch( ev->code ) { case BTN_MOUSE: case BTN_TOUCH: activity = (ev->value != 0); break; default: break; } break; case EV_ABS: switch( ev->code ) { case ABS_MT_POSITION_X: case ABS_MT_POSITION_Y: activity = true; break; case ABS_MT_PRESSURE: case ABS_MT_TOUCH_MAJOR: case ABS_MT_WIDTH_MAJOR: activity = (ev->value > 0); break; case ABS_MT_TRACKING_ID: activity = (ev->value != -1); break; default: break; } break; default: break; } return activity; } /** Handle user_activity_event_pipe notifications * * @param data input_event as void pointer */ static void tklock_datapipe_user_activity_event_cb(gconstpointer data) { static int64_t last_time = 0; const struct input_event *ev = data; if( !ev ) goto EXIT; /* We are only interested in touch activity */ if( !tklock_touch_activity_event_p(ev) ) goto EXIT; /* Deal with autorelock cancellation 1st */ if( autorelock_trigger != AUTORELOCK_NO_TRIGGERS ) { mce_log(LL_DEBUG, "autorelock canceled: touch activity"); autorelock_trigger = AUTORELOCK_NO_TRIGGERS; } /* Touch events relevant unly when handling notification & linger */ if( !(uiexception_type & (UIEXCEPTION_TYPE_NOTIF | UIEXCEPTION_TYPE_LINGER)) ) goto EXIT; int64_t now = mce_lib_get_boot_tick(); if( last_time + 200 > now ) goto EXIT; last_time = now; mce_log(LL_DEBUG, "type: %s, code: %s, value: %d", evdev_get_event_type_name(ev->type), evdev_get_event_code_name(ev->type, ev->code), ev->value); /* N.B. the uiexception_type is bitmask, but only bit at time is * visible in the uiexception_type datapipe */ switch( uiexception_type ) { case UIEXCEPTION_TYPE_LINGER: /* touch events during linger -> do not restore display state */ tklock_uiexception_deny_state_restore(true, "touch event during linger"); break; case UIEXCEPTION_TYPE_NOTIF: /* touch events while device is not locked -> do not restore display state */ if( tklock_uiexception_deny_state_restore(false, "touch event during notification") ) { break; } /* touchscreen activity makes notification exceptions to last longer */ mce_log(LL_DEBUG, "touch event; lengthen notification exception"); tklock_notif_extend_by_renew(); break; default: break; } EXIT: return; } /** Change notifications for init_done */ static void tklock_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)); /* No direct actions, but restoring display state * after notifications etc is disabled until init * done is reached. See tklock_uiexception_begin(). */ EXIT: return; } /** Array of datapipe handlers */ static datapipe_handler_t tklock_datapipe_handlers[] = { // input filters { .datapipe = &submode_pipe, .filter_cb = tklock_datapipe_submode_filter_cb, }, // output triggers { .datapipe = &resume_detected_event_pipe, .output_cb = tklock_datapipe_resume_detected_event_cb, }, { .datapipe = &lipstick_service_state_pipe, .output_cb = tklock_datapipe_lipstick_service_state_cb, }, { .datapipe = &devicelock_service_state_pipe, .output_cb = tklock_datapipe_devicelock_service_state_cb, }, { .datapipe = &osupdate_running_pipe, .output_cb = tklock_datapipe_osupdate_running_cb, }, { .datapipe = &shutting_down_pipe, .output_cb = tklock_datapipe_shutting_down_cb, }, { .datapipe = &devicelock_state_pipe, .output_cb = tklock_datapipe_devicelock_state_cb, }, { .datapipe = &display_state_curr_pipe, .output_cb = tklock_datapipe_display_state_curr_cb, }, { .datapipe = &display_state_next_pipe, .output_cb = tklock_datapipe_display_state_next_cb, }, { .datapipe = &interaction_expected_pipe, .output_cb = tklock_datapipe_interaction_expected_cb, }, { .datapipe = &proximity_sensor_actual_pipe, .output_cb = tklock_datapipe_proximity_sensor_actual_cb, }, { .datapipe = &call_state_pipe, .output_cb = tklock_datapipe_call_state_cb, }, { .datapipe = &music_playback_ongoing_pipe, .output_cb = tklock_datapipe_music_playback_ongoing_cb, }, { .datapipe = &alarm_ui_state_pipe, .output_cb = tklock_datapipe_alarm_ui_state_cb, }, { .datapipe = &charger_state_pipe, .output_cb = tklock_datapipe_charger_state_cb, }, { .datapipe = &battery_status_pipe, .output_cb = tklock_datapipe_battery_status_cb, }, { .datapipe = &uiexception_type_pipe, .output_cb = tklock_datapipe_uiexception_type_cb, }, { .datapipe = &audio_route_pipe, .output_cb = tklock_datapipe_audio_route_cb, }, { .datapipe = &system_state_pipe, .output_cb = tklock_datapipe_system_state_cb, }, { .datapipe = &usb_cable_state_pipe, .output_cb = tklock_datapipe_usb_cable_state_cb, }, { .datapipe = &jack_sense_state_pipe, .output_cb = tklock_datapipe_jack_sense_state_cb, }, { .datapipe = &heartbeat_event_pipe, .output_cb = tklock_datapipe_heartbeat_event_cb, }, { .datapipe = &submode_pipe, .output_cb = tklock_datapipe_submode_cb, }, { .datapipe = &light_sensor_actual_pipe, .output_cb = tklock_datapipe_light_sensor_actual_cb, }, { .datapipe = &lid_sensor_is_working_pipe, .output_cb = tklock_datapipe_lid_sensor_is_working_cb, }, { .datapipe = &lid_sensor_actual_pipe, .output_cb = tklock_datapipe_lid_sensor_actual_cb, }, { .datapipe = &lid_sensor_filtered_pipe, .output_cb = tklock_datapipe_lid_sensor_filtered_cb, }, { .datapipe = &lens_cover_state_pipe, .output_cb = tklock_datapipe_lens_cover_state_cb, }, { .datapipe = &user_activity_event_pipe, .output_cb = tklock_datapipe_user_activity_event_cb, }, { .datapipe = &init_done_pipe, .output_cb = tklock_datapipe_init_done_cb, }, { /* Note: Keybaord slide state signaling must reflect * the actual state -> uses output triggering * unlike the display state logic that is bound * to datapipe input. */ .datapipe = &keyboard_slide_state_pipe, .output_cb = tklock_datapipe_keyboard_slide_output_state_cb, }, { .datapipe = &keyboard_available_state_pipe, .output_cb = tklock_datapipe_keyboard_available_state_cb, }, { .datapipe = &light_sensor_poll_request_pipe, .output_cb = tklock_datapipe_light_sensor_poll_request_cb, }, { .datapipe = &topmost_window_pid_pipe, .output_cb = tklock_datapipe_topmost_window_pid_cb, }, // input triggers { .datapipe = &tklock_request_pipe, .input_cb = tklock_datapipe_tklock_request_cb, }, { .datapipe = &keypress_event_pipe, .input_cb = tklock_datapipe_keypress_event_cb, }, { .datapipe = &lockkey_state_pipe, .input_cb = tklock_datapipe_lockkey_state_cb, }, { .datapipe = &camera_button_state_pipe, .input_cb = tklock_datapipe_camera_button_state_cb, }, { /* Note: Logically we should use output trigger for keyboard slide, * but input triggering is used to avoid turning display * on if mce happens to restart while keyboard is open. * As long as the slide input is not filtered, there is * no harm in this. */ .datapipe = &keyboard_slide_state_pipe, .input_cb = tklock_datapipe_keyboard_slide_input_state_cb, }, // sentinel { .datapipe = 0, } }; static datapipe_bindings_t tklock_datapipe_bindings = { .module = MODULE_NAME, .handlers = tklock_datapipe_handlers, }; /** Append triggers/filters to datapipes */ static void tklock_datapipe_init(void) { mce_datapipe_init_bindings(&tklock_datapipe_bindings); } /** Remove triggers/filters from datapipes */ static void tklock_datapipe_quit(void) { mce_datapipe_quit_bindings(&tklock_datapipe_bindings); } /* ========================================================================= * * AUTOLOCK AFTER DEVICELOCK STATE MACHINE * ========================================================================= */ /** Time limit for triggering autolock after display on */ static int64_t tklock_autolock_on_devlock_limit_trigger = 0; /** Time limit for blocking autolock after lipstick startup */ static int64_t tklock_autolock_on_devlock_limit_block = 0; /** Set autolock blocking limit after lipstick startup */ static void tklock_autolock_on_devlock_block(int duration_ms) { tklock_autolock_on_devlock_limit_block = mce_lib_get_boot_tick() + duration_ms; } static void tklock_autolock_on_devlock_prime(void) { /* While we want to trap only device lock that happens immediately * after unblanking the display, scheduling etc makes it difficult * to specify some exact figure for "immediately". * * Since devicelock timeouts have granularity of 1 minute, assume * that device locking that happens less than 60 seconds after * unblanking was related to what happened during display off time. */ const int autolock_limit = 60 * 1000; /* Do nothing during startup */ if( display_state_curr == MCE_DISPLAY_UNDEF ) goto EXIT; /* Unprime if we are going to powered off state */ switch( display_state_next ) { case MCE_DISPLAY_DIM: case MCE_DISPLAY_ON: break; default: if( tklock_autolock_on_devlock_limit_trigger ) mce_log(LL_DEBUG, "autolock after devicelock: unprimed"); tklock_autolock_on_devlock_limit_trigger = 0; goto EXIT; } /* Prime if we are coming from powered off state */ switch( display_state_curr ) { case MCE_DISPLAY_DIM: case MCE_DISPLAY_ON: break; default: if( !tklock_autolock_on_devlock_limit_trigger ) mce_log(LL_DEBUG, "autolock after devicelock: primed"); tklock_autolock_on_devlock_limit_trigger = mce_lib_get_boot_tick() + autolock_limit; break; } EXIT: return; } static void tklock_autolock_on_devlock_trigger(void) { /* Device lock must be active */ if( devicelock_state != DEVICELOCK_STATE_LOCKED ) goto EXIT; /* Not while handling calls or alarms */ switch( uiexception_type ) { case UIEXCEPTION_TYPE_CALL: case UIEXCEPTION_TYPE_ALARM: goto EXIT; default: break; } /* Autolock time limit must be set and not reached yet */ if( !tklock_autolock_on_devlock_limit_trigger ) goto EXIT; int64_t now = mce_lib_get_boot_tick(); if( now >= tklock_autolock_on_devlock_limit_trigger ) goto EXIT; /* Autolock must not be blocked by recent lipstick restart */ if( now < tklock_autolock_on_devlock_limit_block ) goto EXIT; /* We get here if: Device lock got applied right after * display was powered up. * * Most likely the device lock should have been applied * already when the display was off, but the devicelock * timer did not trigger while the device was suspended. * * It is also possible that the last used application * is still visible and active. * * Setting the tklock moves the application to background * and lockscreen / devicelock is shown instead. */ mce_log(LL_DEBUG, "autolock after devicelock: triggered"); mce_datapipe_request_tklock(TKLOCK_REQUEST_ON); EXIT: return; } /* ========================================================================= * * LID_SENSOR * ========================================================================= */ /** Predicate for: Lid sensor is enabled * * It is assumed that any lid sensors present on the device are always * enabled by default. The mce setting just makes mce either ignore or * act on the change events that might or might not be coming in. * * @return true if lid state changes should be reacted to, false otherwise */ static bool tklock_lidsensor_is_enabled(void) { return lid_sensor_enabled; } /** Initialize lid sensor tracking * * Note: This must be called before installing datapipe callbacks. */ static void tklock_lidsensor_init(void) { /* Initialize state based on flag file presense */ tklock_lid_sensor_is_working = (access(LID_SENSOR_IS_WORKING_FLAG_FILE, F_OK) == 0); mce_log(LL_DEVEL, "lid_sensor_is_working = %s", tklock_lid_sensor_is_working ? "true" : "false"); /* Broadcast initial state */ datapipe_exec_full(&lid_sensor_is_working_pipe, GINT_TO_POINTER(tklock_lid_sensor_is_working)); } /* ========================================================================= * * LID_LIGHT * ========================================================================= */ /** Convert lid light state to human readable string * * @param state lid state value * * @return human readable name of the state */ static const char *tklock_lidlight_repr(tklock_lidlight_t state) { const char *repr = "UNKNOWN"; switch( state ) { case TKLOCK_LIDLIGHT_NA: repr = "NA"; break; case TKLOCK_LIDLIGHT_LO: repr = "LO"; break; case TKLOCK_LIDLIGHT_HI: repr = "HI"; break; default: break; } return repr; } /** Convert lux value to lid light state * * @param lux lux value from light sensor * * @return corresponding lid state value */ static tklock_lidlight_t tklock_lidlight_from_lux(int lux) { /* Sensor is off? */ if( lux < 0 ) return TKLOCK_LIDLIGHT_NA; /* Sensor does not see light? */ if( lux <= filter_lid_als_limit ) return TKLOCK_LIDLIGHT_LO; /* It is not completely dark */ return TKLOCK_LIDLIGHT_HI; } /* ========================================================================= * * LID_FILTER * ========================================================================= */ /** Convert last seen lux value to lid light state * * @return lid state value corresponding with the latest reported lux value */ static tklock_lidlight_t tklock_lidfilter_map_als_state(void) { return tklock_lidlight_from_lux(light_sensor_actual); } /** Predicate for: ALS data is used for filtering Lid sensor state * * @return true if filtering should be done, false otherwise */ static bool tklock_lidfilter_is_enabled(void) { return tklock_lidsensor_is_enabled() && als_enabled && filter_lid_with_als; } /** Flag for: lid=closed + lux=low -> blank display */ static bool tklock_lidfilter_allow_close = false; /** Allow/deny blanking if lid is closed in low light situation */ static void tklock_lidfilter_set_allow_close(bool allow) { if( tklock_lidfilter_allow_close != allow ) { mce_log(LL_DEBUG, "allow_close: %s -> %s", tklock_lidfilter_allow_close ? "true" : "false", allow ? "true" : "false"); tklock_lidfilter_allow_close = allow; } } /** Cached light sensor state */ static tklock_lidlight_t tklock_lidfilter_als_state = TKLOCK_LIDLIGHT_NA; /** Set light sensor state * * @param state TKLOCK_LIDLIGHT_LO/HI/NA */ static void tklock_lidfilter_set_als_state(tklock_lidlight_t state) { if( tklock_lidfilter_als_state != state ) { mce_log(LL_DEBUG, "als_state: %s -> %s", tklock_lidlight_repr(tklock_lidfilter_als_state), tklock_lidlight_repr(state)); tklock_lidfilter_als_state = state; /* Check if futre lid close should be ignored or acted on */ tklock_lidfilter_rethink_allow_close(); } /* If we know we have lo/hi light, stop waiting for als data */ if( tklock_lidfilter_als_state != TKLOCK_LIDLIGHT_NA ) tklock_lidfilter_set_wait_for_light(false); /* If we know we have hi light, stop waiting for darkness*/ if( tklock_lidfilter_als_state == TKLOCK_LIDLIGHT_LO ) tklock_lidfilter_set_wait_for_dark(false); } /** Timer ID for: Stop waiting for lid close event */ static guint tklock_lidfilter_wait_for_close_id = 0; /** Timer Callback for: Stop waiting for lid close event */ static gboolean tklock_lidfilter_wait_for_close_cb(gpointer aptr) { (void)aptr; if( !tklock_lidfilter_wait_for_close_id ) goto EXIT; mce_log(LL_DEBUG, "wait_close: timeout"); tklock_lidfilter_wait_for_close_id = 0; tklock_lidfilter_set_als_state(TKLOCK_LIDLIGHT_NA); tklock_lidfilter_set_allow_close(false); /* Invalidate sensor data */ datapipe_exec_full(&lid_sensor_actual_pipe, GINT_TO_POINTER(COVER_UNDEF)); EXIT: return FALSE; } /** Predicate for: Waiting to see lid close event */ static bool tklock_lidfilter_get_wait_for_close(void) { return tklock_lidfilter_wait_for_close_id != 0; } /** Start/stop waiting for lid close event * * @param state true when expecting lid close, false otherwise * * Used when als drop is noticed while lid is not closed. * * If lid closes soon after, blank screen. * * Otherwise disable blanking until some light is seen. */ static void tklock_lidfilter_set_wait_for_close(bool state) { if( lid_sensor_actual != COVER_OPEN ) state = false; if( display_state_next != MCE_DISPLAY_ON && display_state_next != MCE_DISPLAY_DIM ) state = false; if( state == tklock_lidfilter_get_wait_for_close() ) goto EXIT; mce_log(LL_DEBUG, "wait_close: %s", state ? "start" : "cancel"); if( state ) { tklock_lidfilter_wait_for_close_id = g_timeout_add(TKLOCK_LIDFILTER_SET_WAIT_FOR_CLOSE_DELAY, tklock_lidfilter_wait_for_close_cb, 0); } else { g_source_remove(tklock_lidfilter_wait_for_close_id), tklock_lidfilter_wait_for_close_id = 0; } EXIT: return; } /** Timer ID for: Stop waiting for ALS drop */ static guint tklock_lidfilter_wait_for_dark_id = 0; /** Timer Callback for: Stop waiting for ALS drop */ static gboolean tklock_lidfilter_wait_for_dark_cb(gpointer aptr) { (void)aptr; if( !tklock_lidfilter_wait_for_dark_id ) goto EXIT; mce_log(LL_DEBUG, "wait_dark: timeout"); tklock_lidfilter_wait_for_dark_id = 0; tklock_lidfilter_set_als_state(TKLOCK_LIDLIGHT_NA); /* Invalidate sensor data */ datapipe_exec_full(&lid_sensor_actual_pipe, GINT_TO_POINTER(COVER_UNDEF)); EXIT: return FALSE; } /** Predicate for: Waiting for ALS drop to occur */ static bool tklock_lidfilter_get_wait_for_dark(void) { return tklock_lidfilter_wait_for_dark_id != 0; } /** Start/stop waiting for als drop event * * @param state true when expecting als drop, false otherwise * * Used when lid is closed in non-dark environment. * * If als level drops soon after, blank screen. * * Otherwise ignore lid state until it changes again. */ static void tklock_lidfilter_set_wait_for_dark(bool state) { if( state == tklock_lidfilter_get_wait_for_dark() ) goto EXIT; mce_log(LL_DEBUG, "wait_dark: %s", state ? "start" : "cancel"); if( state ) { tklock_lidfilter_wait_for_dark_id = g_timeout_add(TKLOCK_LIDFILTER_SET_WAIT_FOR_DARK_DELAY, tklock_lidfilter_wait_for_dark_cb, 0); } else { g_source_remove(tklock_lidfilter_wait_for_dark_id), tklock_lidfilter_wait_for_dark_id = 0; } EXIT: return; } /** Timer ID for: Stop waiting for ALS data */ static guint tklock_lidfilter_wait_for_light_id = 0; /** Timer callback for: Stop waiting for ALS data */ static gboolean tklock_lidfilter_wait_for_light_cb(gpointer aptr) { (void)aptr; if( !tklock_lidfilter_wait_for_light_id ) goto EXIT; mce_log(LL_DEBUG, "wait_light: timeout"); tklock_lidfilter_wait_for_light_id = 0; tklock_lidfilter_set_als_state(tklock_lidfilter_map_als_state()); tklock_lidpolicy_rethink(); EXIT: return FALSE; } /** Predicate for: Waiting for ALS data */ static bool tklock_lidfilter_get_wait_for_light(void) { return tklock_lidfilter_wait_for_light_id != 0; } /** Start/stop waiting for als change event * * @param state true when expecting als change, false otherwise * * Used when lid is opened and we need to wait for als powerup. * * If als reports light soon after, unblank screen. * * Otherwise leave display state as it were. */ static void tklock_lidfilter_set_wait_for_light(bool state) { if( state == tklock_lidfilter_get_wait_for_light() ) goto EXIT; mce_log(LL_DEBUG, "wait_light: %s", state ? "start" : "cancel"); if( state ) { tklock_lidfilter_wait_for_light_id = g_timeout_add(TKLOCK_LIDFILTER_SET_WAIT_FOR_LIGHT_DELAY, tklock_lidfilter_wait_for_light_cb, 0); tklock_lidfilter_set_als_state(TKLOCK_LIDLIGHT_NA); tklock_lidpolicy_rethink(); } else { g_source_remove(tklock_lidfilter_wait_for_light_id), tklock_lidfilter_wait_for_light_id = 0; } EXIT: return; } /** React to end of temporary als poll periods */ static void tklock_lidfilter_rethink_als_poll(void) { // when als polling stops, we must stop waiting for light level if( !light_sensor_polling ) { tklock_lidfilter_set_wait_for_light(false); tklock_lidfilter_rethink_als_state(); } } /** Update allow-close flag based on display state and logical als state */ static void tklock_lidfilter_rethink_allow_close(void) { switch( display_state_curr ) { case MCE_DISPLAY_POWER_UP: /* After display power cycling we need to see a high lux value * before lid close can be used for display blanking again. */ tklock_lidfilter_set_allow_close(false); /* Display power up while sensor is in closed state. Assume this * is due to user pressing power key and ignore the lid sensor * state until further changes are received. */ if( lid_sensor_actual == COVER_CLOSED ) { mce_log(LL_DEVEL, "unblank while lid closed; ignore lid"); datapipe_exec_full(&lid_sensor_actual_pipe, GINT_TO_POINTER(COVER_UNDEF)); } break; case MCE_DISPLAY_ON: case MCE_DISPLAY_DIM: case MCE_DISPLAY_LPM_ON: if( tklock_lidfilter_als_state == TKLOCK_LIDLIGHT_HI ) tklock_lidfilter_set_allow_close(true); break; default: break; } } /** Re-evaluate reaction to lid sensor state */ static void tklock_lidfilter_rethink_lid_state(void) { if( !tklock_lidfilter_is_enabled() ) { tklock_lidfilter_set_wait_for_dark(false); tklock_lidfilter_set_wait_for_light(false); tklock_lidfilter_set_wait_for_close(false); goto EXIT; } /* Keep ALS powered up for a while after lid state change */ if( lid_sensor_actual != COVER_UNDEF ) { datapipe_exec_full(&light_sensor_poll_request_pipe, GINT_TO_POINTER(TRUE)); } switch( lid_sensor_actual ) { case COVER_OPEN: tklock_lidfilter_set_wait_for_dark(false); tklock_lidfilter_set_wait_for_light(true); break; case COVER_CLOSED: tklock_lidfilter_set_wait_for_light(false); if( tklock_lidfilter_get_wait_for_close() ) tklock_lidfilter_set_wait_for_close(false); else tklock_lidfilter_set_wait_for_dark(true); break; default: tklock_lidfilter_set_wait_for_dark(false); tklock_lidfilter_set_wait_for_light(false); break; } EXIT: tklock_lidfilter_rethink_als_state(); } /** Re-evaluate reaction to ambient light sensor state * * Augment lid sensor data with als data so that: * * - lid close followed by darkness -> blank * - darkness followed by lid close -> blank * - lid open followed by light seen -> unblank * * Timers are used to set maximum wait periods for the "followed by" * events. In case of timeout the lid state is ignored temporarily or * until the next time it changes. */ static void tklock_lidfilter_rethink_als_state(void) { /* Initialize to "ALS is powered down" value */ static int prev = -1; /* Evaluate sensor state we ought to be in * based on current lux value */ if( tklock_lidfilter_is_enabled() ) { switch( tklock_lidfilter_map_als_state() ) { default: case TKLOCK_LIDLIGHT_NA: /* Ignore: Sensor down time */ break; case TKLOCK_LIDLIGHT_LO: /* Handle: Darkness */ if( tklock_lidfilter_get_wait_for_dark() ) { tklock_lidfilter_set_als_state(TKLOCK_LIDLIGHT_LO); } else if( tklock_lidfilter_get_wait_for_light() ) { tklock_lidfilter_set_als_state(TKLOCK_LIDLIGHT_NA); } else { tklock_lidfilter_set_als_state(TKLOCK_LIDLIGHT_LO); tklock_lidfilter_set_wait_for_close(true); } break; case TKLOCK_LIDLIGHT_HI: /* Handle: Light */ if( tklock_lidfilter_get_wait_for_light() ) { /* During als power up we might see the previously * seen high light value, but rise in level means * the sensor is up and sees light -> we can stop * waiting */ if( prev < light_sensor_actual ) tklock_lidfilter_set_als_state(TKLOCK_LIDLIGHT_HI); else tklock_lidfilter_set_als_state(TKLOCK_LIDLIGHT_NA); } else if( tklock_lidfilter_get_wait_for_dark() ) { tklock_lidfilter_set_als_state(TKLOCK_LIDLIGHT_NA); } else { tklock_lidfilter_set_als_state(TKLOCK_LIDLIGHT_HI); } break; } } /* Update previous value unless ALS is powered down */ if( light_sensor_actual >= 0 ) prev = light_sensor_actual; tklock_lidpolicy_rethink(); } /* ========================================================================= * * LID_POLICY * ========================================================================= */ /** Evaluate lid policy state based on lid and light sensor states * * While lid cover sensor use is enabled, by default: * * - Closing lid blanks the screen and activates lockscreen * - Opening lid unblanks the screen * * Settings can be used to: * * - Select whether lid sensor state should be applied as such or * augmented by tracking ambient light sensor based heuristics * to avoid possible false positives from the lid sensor itself * * - Select what actions are taken when the policy change occurs */ static void tklock_lidpolicy_rethink(void) { /* We have not seen COVER_CLOSED state yet */ static bool lid_has_been_closed = false; /* Assume lid is neither open nor closed */ cover_state_t action = COVER_UNDEF; /* Evaluate required policy state */ if( !tklock_lidsensor_is_enabled() ) { /* The lid sensor is not used */ } else if( !tklock_lid_sensor_is_working ) { /* No policy decisions until the sensor is known to work */ } else if( !tklock_lidfilter_is_enabled() ) { /* No filtering -> use sensor state as is */ action = lid_sensor_actual; } else if( lid_sensor_actual == COVER_CLOSED && tklock_lidfilter_als_state == TKLOCK_LIDLIGHT_LO ) { if( tklock_lidfilter_allow_close ) action = COVER_CLOSED; } else if( lid_sensor_actual == COVER_OPEN && tklock_lidfilter_als_state == TKLOCK_LIDLIGHT_HI ) { action = COVER_OPEN; } /* To avoid unblanking on mce restart while lid is open, stay in * undecided state until we have observed lid closed state too. */ if( action == COVER_OPEN && !lid_has_been_closed ) action = COVER_UNDEF; /* Skip the rest if there is no change */ if( lid_sensor_filtered == action ) goto EXIT; mce_log(LL_DEBUG, "lid policy: %s -> %s", cover_state_repr(lid_sensor_filtered), cover_state_repr(action)); /* First make the policy decision known */ datapipe_exec_full(&lid_sensor_filtered_pipe, GINT_TO_POINTER(action)); /* Then execute the required actions */ switch( action ) { case COVER_CLOSED: /* Allow unblanking when lid is opened again. */ lid_has_been_closed = true; /* Blank display + lock ui */ if( tklock_lid_close_actions != LID_CLOSE_ACTION_DISABLED ) { mce_log(LL_DEVEL, "lid closed - blank"); mce_datapipe_request_display_state(MCE_DISPLAY_OFF); } if( tklock_lid_close_actions == LID_CLOSE_ACTION_TKLOCK ) { mce_log(LL_DEBUG, "lid closed - tklock"); mce_datapipe_request_tklock(TKLOCK_REQUEST_ON); } break; case COVER_OPEN: /* Unblank display + unlock ui */ if( tklock_lid_open_actions != LID_OPEN_ACTION_DISABLED ) { mce_log(LL_DEVEL, "lid open - unblank"); mce_datapipe_request_display_state(MCE_DISPLAY_ON); } if( tklock_lid_open_actions == LID_OPEN_ACTION_TKUNLOCK ) { mce_log(LL_DEBUG, "lid open - untklock"); mce_datapipe_request_tklock(TKLOCK_REQUEST_OFF); } break; default: mce_log(LL_DEBUG, "lid ignored"); /* NOP */ break; } EXIT: return; } /* ========================================================================= * * KEYBOARD SLIDE STATE MACHINE * ========================================================================= */ static void tklock_keyboard_slide_opened(void) { /* In any case opening the kbd slide will cancel * other autorelock triggers */ if( autorelock_trigger != AUTORELOCK_NO_TRIGGERS ) { mce_log(LL_DEBUG, "autorelock canceled: kbd slide opened"); autorelock_trigger = AUTORELOCK_NO_TRIGGERS; } /* Display must be off */ switch( display_state_next ) { case MCE_DISPLAY_ON: case MCE_DISPLAY_DIM: goto EXIT; default: break; } /* Check if actions are wanted */ switch( tklock_kbd_open_trigger ) { default: case KBD_OPEN_TRIGGER_NEVER: goto EXIT; case KBD_OPEN_TRIGGER_ALWAYS: break; case KBD_OPEN_TRIGGER_NO_PROXIMITY: if( proximity_sensor_actual != COVER_OPEN || lid_sensor_filtered == COVER_CLOSED ) goto EXIT; break; } /* Check what actions are wanted */ if( tklock_kbd_open_actions != LID_OPEN_ACTION_DISABLED ) { mce_log(LL_DEVEL, "kbd slide open - unblank"); mce_datapipe_request_display_state(MCE_DISPLAY_ON); } if( tklock_kbd_open_actions == LID_OPEN_ACTION_TKUNLOCK ) { mce_log(LL_DEBUG, "kbd slide open - untklock"); mce_datapipe_request_tklock(TKLOCK_REQUEST_OFF); } /* Mark down we unblanked due to keyboard open */ mce_log(LL_DEBUG, "autorelock primed: on kbd slide close"); autorelock_trigger = AUTORELOCK_KBD_SLIDE; EXIT: return; } /** Wait for proximity sensor -callback for keyboard slide handling * * @param aptr unused */ static void tklock_keyboard_slide_opened_cb(gpointer aptr) { (void)aptr; /* Slide still open? */ if( keyboard_slide_input_state == COVER_OPEN ) { tklock_keyboard_slide_opened(); } } static void tklock_keyboard_slide_closed(void) { /* Must not blank during active alarms / calls */ if( uiexception_type & (UIEXCEPTION_TYPE_CALL | UIEXCEPTION_TYPE_ALARM) ) goto EXIT; /* Check if actions are wanted */ switch( tklock_kbd_close_trigger ) { default: case KBD_CLOSE_TRIGGER_NEVER: goto EXIT; case KBD_CLOSE_TRIGGER_ALWAYS: break; case KBD_CLOSE_TRIGGER_AFTER_OPEN: if( autorelock_trigger != AUTORELOCK_KBD_SLIDE ) goto EXIT; mce_log(LL_DEBUG, "autorelock triggered: kbd slide closed"); autorelock_trigger = AUTORELOCK_NO_TRIGGERS; break; } /* Check what actions are wanted */ if( tklock_kbd_close_actions != LID_CLOSE_ACTION_DISABLED ) { mce_log(LL_DEVEL, "kbd slide closed - blank"); mce_datapipe_request_display_state(MCE_DISPLAY_OFF); } if( tklock_kbd_close_actions == LID_CLOSE_ACTION_TKLOCK ) { mce_log(LL_DEBUG, "kbd slide closed - tklock"); mce_datapipe_request_tklock(TKLOCK_REQUEST_ON); } EXIT: /* In any case closing the kbd slide will cancel autorelock triggers */ if( autorelock_trigger != AUTORELOCK_NO_TRIGGERS ) { mce_log(LL_DEBUG, "autorelock canceled: kbd slide closed"); autorelock_trigger = AUTORELOCK_NO_TRIGGERS; } return; } static void tklock_keyboard_slide_rethink(void) { switch( keyboard_slide_input_state ) { case COVER_OPEN: /* Delay processing until proximity sensor state is known */ common_on_proximity_schedule(MODULE_NAME, tklock_keyboard_slide_opened_cb, 0); break; case COVER_CLOSED: tklock_keyboard_slide_closed(); break; default: break; } } /* ========================================================================= * * AUTOLOCK STATE MACHINE * * Automatically apply tklock when * 1) display has been off for tklock_autolock_delay ms * 2) autolocking is enabled * 3) we are not handling call/alarm/etc * * ========================================================================= */ static int64_t tklock_autolock_tick = MAX_TICK; static mce_hbtimer_t *tklock_autolock_timer = 0; static void tklock_autolock_evaluate(void) { // display must be currently off if( display_state_curr != MCE_DISPLAY_OFF ) goto EXIT; // tklock unset if( tklock_datapipe_in_tklock_submode() ) goto EXIT; // autolocking enabled if( !tk_autolock_enabled ) goto EXIT; // not handling calls, alarms, etc if( uiexception_type != UIEXCEPTION_TYPE_NONE ) goto EXIT; // if device lock is on, apply tklock immediately if( devicelock_state == DEVICELOCK_STATE_LOCKED ) goto LOCK; // autolock delay to passed if( mce_lib_get_boot_tick() < tklock_autolock_tick ) goto EXIT; LOCK: mce_log(LL_DEBUG, "autolock applied"); tklock_ui_set_enabled(true); EXIT: return; } static gboolean tklock_autolock_cb(gpointer aptr) { (void)aptr; tklock_autolock_tick = MIN_TICK; mce_log(LL_DEBUG, "autolock timer triggered"); tklock_autolock_evaluate(); return FALSE; } static void tklock_autolock_disable(void) { tklock_autolock_tick = MAX_TICK; if( !mce_hbtimer_is_active(tklock_autolock_timer) ) goto EXIT; mce_hbtimer_stop(tklock_autolock_timer); mce_log(LL_DEBUG, "autolock timer stopped"); EXIT: return; } static void tklock_autolock_enable(void) { if( mce_hbtimer_is_active(tklock_autolock_timer) ) goto EXIT; int delay = mce_clip_int(MINIMUM_AUTOLOCK_DELAY, MAXIMUM_AUTOLOCK_DELAY, tklock_autolock_delay); tklock_autolock_tick = mce_lib_get_boot_tick() + delay; mce_hbtimer_set_period(tklock_autolock_timer, delay); mce_hbtimer_start(tklock_autolock_timer); mce_log(LL_DEBUG, "autolock timer started (%d ms)", delay); EXIT: return; } static void tklock_autolock_rethink(void) { if( display_state_next != MCE_DISPLAY_OFF ) { // not in OFF or moving away from OFF tklock_autolock_disable(); } else if( display_state_next != display_state_curr ) { // making transition to OFF tklock_autolock_enable(); } else { // stable display OFF state tklock_autolock_evaluate(); } } static void tklock_autolock_init(void) { tklock_autolock_timer = mce_hbtimer_create("autolock-timer", tklock_autolock_delay, tklock_autolock_cb, 0); } static void tklock_autolock_quit(void) { mce_hbtimer_delete(tklock_autolock_timer), tklock_autolock_timer = 0; } /* ========================================================================= * * PROXIMITY LOCKING STATE MACHINE * * Automatically apply tklock when * 1) display has been off for PROXLOC_DELAY_MS * 2) proximity sensor is covered * 3) we are not handling call/alarm/etc * ========================================================================= */ /** Proximity sensor on-demand tag for proximity locking purposes */ #define PROXLOC_ON_DEMAND_TAG "proxlock" /** Delay for enabling tklock from display off when proximity is covered */ #define PROXLOC_DELAY_MS (3000) static int64_t tklock_proxlock_tick = MAX_TICK; static guint tklock_proxlock_id = 0; static void tklock_proxlock_evaluate(void) { // display must be currently off if( display_state_curr != MCE_DISPLAY_OFF ) goto EXIT; // tklock unset if( tklock_datapipe_in_tklock_submode() ) goto EXIT; // proximity covered if( proximity_sensor_effective != COVER_CLOSED ) goto EXIT; // not handling call, alarm, etc if( uiexception_type != UIEXCEPTION_TYPE_NONE ) goto EXIT; // proximity lock delay passed if( mce_lib_get_boot_tick() < tklock_proxlock_tick ) goto EXIT; // lock mce_log(LL_DEBUG, "proxlock applied"); tklock_ui_set_enabled(true); EXIT: return; } static gboolean tklock_proxlock_cb(gpointer aptr) { (void)aptr; if( tklock_proxlock_id ) { tklock_proxlock_id = 0; tklock_proxlock_tick = MIN_TICK; mce_log(LL_DEBUG, "proxlock timer triggered"); tklock_proxlock_evaluate(); /* Timer did not get re-activated, ps not needed anymore */ if( !tklock_proxlock_id ) datapipe_exec_full(&proximity_sensor_required_pipe, PROXIMITY_SENSOR_REQUIRED_REM PROXLOC_ON_DEMAND_TAG); } return false; } static void tklock_proxlock_disable(void) { tklock_proxlock_tick = MAX_TICK; if( tklock_proxlock_id ) { g_source_remove(tklock_proxlock_id), tklock_proxlock_id = 0; mce_log(LL_DEBUG, "proxlock timer stopped"); /* Timer canceled, ps not needed anymore */ datapipe_exec_full(&proximity_sensor_required_pipe, PROXIMITY_SENSOR_REQUIRED_REM PROXLOC_ON_DEMAND_TAG); } } static void tklock_proxlock_enable(void) { int delay = PROXLOC_DELAY_MS; if( !tklock_proxlock_id ) { tklock_proxlock_tick = mce_lib_get_boot_tick() + delay; tklock_proxlock_id = g_timeout_add(delay, tklock_proxlock_cb, 0); mce_log(LL_DEBUG, "proxlock timer started (%d ms)", delay); /* Timer started, ps is needed */ datapipe_exec_full(&proximity_sensor_required_pipe, PROXIMITY_SENSOR_REQUIRED_ADD PROXLOC_ON_DEMAND_TAG); } } static void tklock_proxlock_resume(void) { /* Do we have a timer to re-evaluate? */ if( !tklock_proxlock_id ) goto EXIT; /* Clear old timer */ g_source_remove(tklock_proxlock_id), tklock_proxlock_id = 0; int64_t now = mce_lib_get_boot_tick(); if( now >= tklock_proxlock_tick ) { /* Opportunistic triggering on resume */ mce_log(LL_DEBUG, "proxlock time passed while suspended"); tklock_proxlock_tick = MIN_TICK; tklock_proxlock_evaluate(); } else { /* Re-calculate wakeup time */ int delay = (int)(tklock_proxlock_tick - now); mce_log(LL_DEBUG, "adjusting proxlock time after resume (%d ms)", delay); tklock_proxlock_id = g_timeout_add(delay, tklock_proxlock_cb, 0); } /* Timer canceled, ps not needed anymore */ if( !tklock_proxlock_id ) datapipe_exec_full(&proximity_sensor_required_pipe, PROXIMITY_SENSOR_REQUIRED_REM PROXLOC_ON_DEMAND_TAG); EXIT: return; } static void tklock_proxlock_rethink(void) { if( display_state_next != MCE_DISPLAY_OFF ) { // not in OFF or moving away from OFF tklock_proxlock_disable(); } else if( display_state_next != display_state_curr ) { // making transition to OFF tklock_proxlock_enable(); } else { // check if proxlock conditions are met tklock_proxlock_evaluate(); } } /* ========================================================================= * * UI EXCEPTION HANDLING STATE MACHINE * ========================================================================= */ typedef struct { uiexception_type_t mask; uiexception_type_t last; display_state_t display; bool tklock; devicelock_state_t devicelock; bool insync; bool restore; bool was_called; int64_t linger_tick; guint linger_id; int64_t notif_tick; guint notif_id; } exception_t; static exception_t exdata = { .mask = UIEXCEPTION_TYPE_NONE, .last = UIEXCEPTION_TYPE_NONE, .display = MCE_DISPLAY_UNDEF, .tklock = false, .devicelock = DEVICELOCK_STATE_UNDEFINED, .insync = true, .restore = true, .was_called = false, .linger_tick = MIN_TICK, .linger_id = 0, .notif_tick = MIN_TICK, .notif_id = 0, }; static uiexception_type_t topmost_active(uiexception_type_t mask) { /* Assume UI side priority is: * 1. notification dialogs * 2. alarm ui * 3. call ui * 4. rest */ static const uiexception_type_t pri[] = { UIEXCEPTION_TYPE_NOTIF, UIEXCEPTION_TYPE_ALARM, UIEXCEPTION_TYPE_CALL, UIEXCEPTION_TYPE_LINGER, UIEXCEPTION_TYPE_NOANIM, 0 }; for( size_t i = 0; pri[i]; ++i ) { if( mask & pri[i] ) return pri[i]; } return UIEXCEPTION_TYPE_NONE; } static void tklock_uiexception_sync_to_datapipe(void) { uiexception_type_t in_pipe = datapipe_get_gint(uiexception_type_pipe); uiexception_type_t active = topmost_active(exdata.mask); if( in_pipe != active ) { datapipe_exec_full(&uiexception_type_pipe, GINT_TO_POINTER(active)); } } /** Do not restore display/tklock state at the end of exceptional ui state * * @param force true for unconditionally canceling the state restore; or * false for canceling only if neither tklock nor devicelock * is active */ static bool tklock_uiexception_deny_state_restore(bool force, const char *cause) { bool changed = false; // must have restore to deny if( !exdata.restore || !exdata.mask ) goto EXIT; // must be forced or unlocked if( !force && (exdata.tklock || exdata.devicelock) ) goto EXIT; mce_log(LL_DEVEL, "%s; state restore disabled", cause); exdata.restore = false; changed = true; EXIT: return changed; } static void tklock_uiexception_rethink(void) { static display_state_t display_prev = MCE_DISPLAY_UNDEF; static call_state_t call_state_prev = CALL_STATE_INVALID; static uiexception_type_t active_prev = UIEXCEPTION_TYPE_NONE; bool activate = false; bool blank = false; uiexception_type_t active = topmost_active(exdata.mask); bool proximity_blank = false; /* Make sure "proximityblanking" state gets cleared if display * changes to non-off state. */ if( display_prev != display_state_curr ) { switch( display_state_curr ) { case MCE_DISPLAY_OFF: case MCE_DISPLAY_POWER_DOWN: break; default: case MCE_DISPLAY_ON: case MCE_DISPLAY_DIM: case MCE_DISPLAY_UNDEF: case MCE_DISPLAY_LPM_OFF: case MCE_DISPLAY_LPM_ON: case MCE_DISPLAY_POWER_UP: datapipe_exec_full(&proximity_blanked_pipe, GINT_TO_POINTER(false)); break; } } if( !active ) { mce_log(LL_DEBUG, "UIEXCEPTION_TYPE_NONE"); goto EXIT; } /* Track states that have gotten topmost before linger */ if( active != UIEXCEPTION_TYPE_LINGER ) exdata.last = UIEXCEPTION_TYPE_NONE; else if( active_prev != UIEXCEPTION_TYPE_LINGER ) exdata.last = active_prev; /* Special case: tklock changes during incoming calls */ if( exdata.tklock ) { switch( call_state ) { case CALL_STATE_RINGING: /* When UI side is dealing with incoming call, it removes tklock * so that peeking shows home screen instead of lock screen. And * since we do not want that to cancel the state restoration after * the call ends -> we need to ignore it. */ if( !exdata.was_called ) { mce_log(LL_NOTICE, "starting to ignore tklock removal"); exdata.was_called = true; } break; case CALL_STATE_NONE: /* Start paying attention to tklock changes again if it gets * restored after all calls have ended */ if( exdata.was_called && tklock_datapipe_in_tklock_submode() ) { mce_log(LL_NOTICE, "stopping to ignore tklock removal"); exdata.was_called = false; } break; default: break; } } /* Canceling state restore due to tklock changes */ if( tklock_datapipe_in_tklock_submode() ) { // getting locked does not cancel state restore exdata.tklock = true; } else if( exdata.tklock && !exdata.was_called && exdata.restore ) { // but getting unlocked outside incoming call does mce_log(LL_NOTICE, "DISABLING STATE RESTORE; tklock out of sync"); exdata.restore = false; } /* Canceling state restore due to device lock changes */ if( devicelock_state == DEVICELOCK_STATE_LOCKED ) { // getting locked does not cancel state restore exdata.devicelock = devicelock_state; } else if( exdata.devicelock != devicelock_state && exdata.restore ) { // but getting unlocked does mce_log(LL_NOTICE, "DISABLING STATE RESTORE; devicelock out of sync"); exdata.restore = false; } /* Re-sync on incoming call */ if( call_state_prev != call_state ) { if( !exdata.insync && call_state == CALL_STATE_RINGING ) { mce_log(LL_NOTICE, "incoming call; assuming in sync again"); exdata.insync = true; } call_state_prev = call_state; } // re-sync on display on transition if( display_prev != display_state_curr ) { mce_log(LL_DEBUG, "display state: %s -> %s", display_state_repr(display_prev), display_state_repr(display_state_curr)); if( display_state_curr == MCE_DISPLAY_ON ) { if( !exdata.insync ) mce_log(LL_NOTICE, "display unblanked; assuming in sync again"); exdata.insync = true; } } // re-sync on active exception change if( active_prev != active ) { active_prev = active; if( !exdata.insync ) mce_log(LL_NOTICE, "exception state changed; assuming in sync again"); exdata.insync = true; } switch( active ) { case UIEXCEPTION_TYPE_NOANIM: /* The noanim exception is used only during display power up. * It also has the lowest priority, which means that if it * ever gets on top of the exception stack, we need to disable * state restore. */ if( exdata.restore ) { mce_log(LL_DEBUG, "noanim exception state; disable state restore"); exdata.restore = false; } break; case UIEXCEPTION_TYPE_NOTIF: mce_log(LL_DEBUG, "UIEXCEPTION_TYPE_NOTIF"); activate = true; break; case UIEXCEPTION_TYPE_ALARM: mce_log(LL_DEBUG, "UIEXCEPTION_TYPE_ALARM"); activate = true; break; case UIEXCEPTION_TYPE_CALL: mce_log(LL_DEBUG, "UIEXCEPTION_TYPE_CALL"); if( call_state == CALL_STATE_RINGING ) { mce_log(LL_DEBUG, "call=RINGING; activate"); activate = true; } else if( audio_route != AUDIO_ROUTE_HANDSET ) { mce_log(LL_DEBUG, "audio!=HANDSET; activate"); activate = true; } else if( proximity_sensor_effective == COVER_CLOSED ) { mce_log(LL_DEBUG, "proximity=COVERED; blank"); /* blanking due to proximity sensor */ blank = proximity_blank = true; } else { mce_log(LL_DEBUG, "proximity=NOT-COVERED; activate"); activate = true; } break; case UIEXCEPTION_TYPE_LINGER: mce_log(LL_DEBUG, "UIEXCEPTION_TYPE_LINGER"); activate = true; break; case UIEXCEPTION_TYPE_NONE: // we should not get here break; default: // added new states and forgot to update state machine? mce_log(LL_CRIT, "unknown ui exception %d; have to ignore", active); mce_abort(); break; } mce_log(LL_DEBUG, "blank=%d, activate=%d", blank, activate); if( blank ) { if( display_state_curr != MCE_DISPLAY_OFF ) { /* expose blanking due to proximity via datapipe */ if( proximity_blank ) { mce_log(LL_DEVEL, "display proximity blank"); datapipe_exec_full(&proximity_blanked_pipe, GINT_TO_POINTER(true)); } else { mce_log(LL_DEBUG, "display blank"); } mce_datapipe_request_display_state(MCE_DISPLAY_OFF); } else { mce_log(LL_DEBUG, "display already blanked"); } } else if( activate ) { if( display_prev == MCE_DISPLAY_ON && display_state_curr != MCE_DISPLAY_ON ) { /* Assume: dim/blank timer took over the blanking. * Disable this state machine until display gets * turned back on */ mce_log(LL_NOTICE, "AUTO UNBLANK DISABLED; display out of sync"); exdata.insync = false; /* Disable state restore, unless we went out of * sync during call ui handling */ if( exdata.restore && active != UIEXCEPTION_TYPE_CALL ) { exdata.restore = false; mce_log(LL_NOTICE, "DISABLING STATE RESTORE; display out of sync"); } } else if( !exdata.insync ) { mce_log(LL_NOTICE, "NOT UNBLANKING; still out of sync"); } else if( lid_sensor_filtered == COVER_CLOSED ) { mce_log(LL_NOTICE, "NOT UNBLANKING; lid covered"); } else if( proximity_sensor_effective != COVER_OPEN ) { mce_log(LL_NOTICE, "NOT UNBLANKING; proximity covered"); } else if( display_state_curr != MCE_DISPLAY_ON ) { mce_log(LL_DEBUG, "display unblank"); mce_datapipe_request_display_state(MCE_DISPLAY_ON); } } /* Make sure "proximityblanking" state gets cleared if display * state is no longer controlled by this state machine. */ if( !exdata.insync ) { datapipe_exec_full(&proximity_blanked_pipe, GINT_TO_POINTER(false)); } EXIT: display_prev = display_state_curr; return; } static void tklock_uiexception_cancel(void) { if( exdata.notif_id ) { g_source_remove(exdata.notif_id), exdata.notif_id = 0; } if( exdata.linger_id ) { g_source_remove(exdata.linger_id), exdata.linger_id = 0; } exdata.mask = UIEXCEPTION_TYPE_NONE; exdata.last = UIEXCEPTION_TYPE_NONE; exdata.display = MCE_DISPLAY_UNDEF; exdata.tklock = false; exdata.devicelock = DEVICELOCK_STATE_UNDEFINED; exdata.insync = true; exdata.restore = true; exdata.was_called = false; exdata.linger_tick = MIN_TICK; exdata.linger_id = 0; exdata.notif_tick = MIN_TICK, exdata.notif_id = 0; } static void tklock_uiexception_finish(void) { /* operate on copy of data, in case the data * pipe operations cause feedback */ exception_t exx = exdata; tklock_uiexception_cancel(); /* update exception data pipe first */ tklock_uiexception_sync_to_datapipe(); /* check if restoring has been blocked */ if( !exx.restore ) goto EXIT; /* then flip the tklock back on? Note that we * we do not unlock no matter what. */ if( exx.tklock ) { mce_datapipe_request_tklock(TKLOCK_REQUEST_ON); } /* and finally the display data pipe */ switch( exx.display ) { default: /* If the display was not clearly ON when exception started, * turn it OFF after exceptions are over. */ mce_datapipe_request_display_state(MCE_DISPLAY_OFF); break; case MCE_DISPLAY_ON: case MCE_DISPLAY_DIM: /* Unblank only if proximity sensor is not covered when * the linger time has passed. * * Note: Because linger times are relatively short, * we use raw sensor data here instead of the filtered * proximity_sensor_effective that is normally used * with unblanking policies. */ if( proximity_sensor_actual != COVER_OPEN || lid_sensor_filtered == COVER_CLOSED ) break; mce_datapipe_request_display_state(exx.display); break; } EXIT: return; } static gboolean tklock_uiexception_linger_cb(gpointer aptr) { (void) aptr; if( !exdata.linger_id ) goto EXIT; /* mark timer inactive */ exdata.linger_id = 0; /* Ignore unless linger bit and only linger bit is set */ if( exdata.mask != UIEXCEPTION_TYPE_LINGER ) { mce_log(LL_WARN, "spurious linger timeout"); goto EXIT; } mce_log(LL_DEBUG, "linger timeout"); /* Disable state restore if lockscreen is active and interaction * expected after linger. */ if( display_state_next == MCE_DISPLAY_ON && tklock_ui_is_enabled() && interaction_expected ) { if( exdata.last == UIEXCEPTION_TYPE_CALL ) { /* End of call is exception within exception because * the call ui can be left on top of the lockscreen and * there is no way to know whether that happened or not. * * Do not disable state restore and assume the linger * time has been long enough for the user to have done * significant enough actions during it to have disabled * the state restore in other ways. */ } else { tklock_uiexception_deny_state_restore(true, "interaction during linger"); } } tklock_uiexception_finish(); EXIT: return FALSE; } static void tklock_uiexception_end(uiexception_type_t type, int64_t linger) { if( !(exdata.mask & type) ) goto EXIT; int64_t now = mce_lib_get_boot_tick(); exdata.mask &= ~type; linger += now; if( exdata.linger_tick < linger ) exdata.linger_tick = linger; if( exdata.linger_id ) g_source_remove(exdata.linger_id), exdata.linger_id = 0; if( !exdata.mask ) { int delay = (int)(exdata.linger_tick - now); if( delay > 0 ) { mce_log(LL_DEBUG, "finish after %d ms linger", delay); exdata.mask |= UIEXCEPTION_TYPE_LINGER; exdata.linger_id = g_timeout_add(delay, tklock_uiexception_linger_cb, 0); } else { mce_log(LL_DEBUG, "finish without linger"); tklock_uiexception_finish(); } } tklock_uiexception_sync_to_datapipe(); EXIT: return; } static void tklock_uiexception_begin(uiexception_type_t type, int64_t linger) { if( !exdata.mask ) { /* reset existing stats */ tklock_uiexception_cancel(); /* save display, tklock and device lock states */ exdata.display = display_state_next; exdata.tklock = tklock_datapipe_in_tklock_submode(); exdata.devicelock = devicelock_state; /* initially insync, restore state at end */ exdata.insync = true; exdata.restore = (type != UIEXCEPTION_TYPE_NOANIM); /* Display should be on after booting up to user mode. * If something like "charger connected" notification gets * triggered during bootup, we need to disable state restore * in order not to cause return to some non-intentional * transient state. */ if( exdata.restore && init_done != TRISTATE_TRUE && system_state == MCE_SYSTEM_STATE_USER ) { mce_log(LL_DEVEL, "suppressing display state restore"); exdata.restore = false; } } exdata.mask &= ~UIEXCEPTION_TYPE_LINGER; exdata.mask |= type; int64_t now = mce_lib_get_boot_tick(); linger += now; if( exdata.linger_tick < linger ) exdata.linger_tick = linger; if( exdata.linger_id ) g_source_remove(exdata.linger_id), exdata.linger_id = 0; tklock_uiexception_sync_to_datapipe(); } /* ========================================================================= * * LOW POWER MODE UI STATE MACHINE * ========================================================================= */ /** Bitmap of automatic lpm triggering modes */ static gint tklock_lpmui_triggering = MCE_DEFAULT_TK_LPMUI_TRIGGERING; static guint tklock_lpmui_triggering_setting_id = 0; /* Proximity change time limits for low power mode triggering */ enum { /** Minimum time [ms] the proximity needs to be in stable state */ LPMUI_LIM_STABLE = 3000, /** Maximum time [ms] in between proximity changes */ LPMUI_LIM_CHANGE = 1500, }; /** The latest lpm ui state that was broadcast; initialized to invalid value */ static int tklock_lpmui_state_signaled = -1; /** The currently wanted lpm ui state; initialized to invalid value */ static int tklock_lpmui_state_wanted = -1; /** Set lpm ui state * * Broadcast changes over D-Bus * * @param enable true if lpm ui should be enabled, false otherwise */ static void tklock_lpmui_set_state(bool enable) { if( tklock_lpmui_state_wanted == enable ) goto EXIT; tklock_lpmui_state_wanted = enable; if( enable ) { /* The LPM lockscreen is activated when both tklock and * lpm state are set. To avoid going through normal * lockscreen state, send lpm indication 1st */ tklock_ui_send_lpm_signal(); /* Make sure ui locking is initiated before we enter LPM * display modes, the dbus signaling happens after some * delay. */ mce_datapipe_request_tklock(TKLOCK_REQUEST_ON); } else { /* Do delayed signaling in sync with possible tklock * state changes. */ tklock_ui_notify_schdule(); } EXIT: return; } /** Reset LPM UI proximity sensor history * * Triggering LPM UI is not possible until stable state is * reached again. */ static void tklock_lpmui_reset_history(void) { int64_t now = mce_lib_get_boot_tick(); for( size_t i = 0; i < numof(tklock_lpmui_hist); ++i ) { tklock_lpmui_hist[i].tick = now; tklock_lpmui_hist[i].state = proximity_sensor_actual; } } /** Update LPM UI proximity sensor history * * @param state proximity sensor state (raw, undelayed) */ static void tklock_lpmui_update_history(cover_state_t state) { if( state == tklock_lpmui_hist[0].state ) goto EXIT; memmove(tklock_lpmui_hist+1, tklock_lpmui_hist+0, sizeof tklock_lpmui_hist - sizeof *tklock_lpmui_hist); tklock_lpmui_hist[0].tick = mce_lib_get_boot_tick(); tklock_lpmui_hist[0].state = state; EXIT: return; } /** Check if LPM UI proximity sensor history equals "out of pocket" state * * Proximity was covered for LPMUI_LIM_STABLE ms, then uncovered less * than LPMUI_LIM_CHANGE ms ago. * * @return true if conditions met, false otherwise */ static bool tklock_lpmui_probe_from_pocket(void) { bool res = false; if( !(tklock_lpmui_triggering & LPMUI_TRIGGERING_FROM_POCKET) ) goto EXIT; int64_t now = mce_lib_get_boot_tick(); int64_t t; /* Uncovered < LPMUI_LIM_CHANGE ms ago ? */ if( tklock_lpmui_hist[0].state != COVER_OPEN ) goto EXIT; t = now - tklock_lpmui_hist[0].tick; if( t > LPMUI_LIM_CHANGE ) goto EXIT; /* After being covered for LPMUI_LIM_STABLE ms ? */ if( tklock_lpmui_hist[1].state != COVER_CLOSED ) goto EXIT; t = tklock_lpmui_hist[0].tick - tklock_lpmui_hist[1].tick; if( t < LPMUI_LIM_STABLE ) goto EXIT; res = true; EXIT: return res; } /** Check if LPM UI proximity sensor history equals "covered on table" state * * Proximity was uncovered for LPMUI_LIM_STABLE ms, them covered and * uncovered within LPMUI_LIM_CHANGE ms, possibly several times. * * @return true if conditions met, false otherwise */ static bool tklock_lpmui_probe_on_table(void) { bool res = false; if( !(tklock_lpmui_triggering & LPMUI_TRIGGERING_HOVER_OVER) ) goto EXIT; int64_t t = mce_lib_get_boot_tick(); for( size_t i = 0; ; i += 2 ) { /* Need to check 3 slots: OPEN, CLOSED, OPEN */ if( i + 3 > numof(tklock_lpmui_hist) ) goto EXIT; /* Covered and uncovered within LPMUI_LIM_CHANGE ms? */ if( tklock_lpmui_hist[i+0].state != COVER_OPEN ) goto EXIT; if( t - tklock_lpmui_hist[i+0].tick > LPMUI_LIM_CHANGE ) goto EXIT; if( tklock_lpmui_hist[i+1].state != COVER_CLOSED ) goto EXIT; if( t - tklock_lpmui_hist[i+1].tick > LPMUI_LIM_CHANGE ) goto EXIT; /* After being uncovered longer than LPMUI_LIM_STABLE ms? */ if( tklock_lpmui_hist[i+2].state != COVER_OPEN ) goto EXIT; t = tklock_lpmui_hist[i+1].tick - tklock_lpmui_hist[i+2].tick; if( t > LPMUI_LIM_STABLE ) break; t = tklock_lpmui_hist[i+1].tick; } res = true; EXIT: return res; } /** Check if proximity sensor history should trigger LPM UI mode * * @return true if LPM UI can be enabled, false otherwise */ static bool tklock_lpmui_probe(void) { bool glance = false; if( tklock_lpmui_probe_from_pocket() ) { mce_log(LL_DEBUG, "from pocket"); glance = true; } else if( tklock_lpmui_probe_on_table() ) { mce_log(LL_DEBUG, "hovering over"); glance = true; } else { mce_log(LL_DEBUG, "proximity noise"); } return glance; } /** Check if LPM UI mode should be enabled */ static void tklock_lpmui_rethink(void) { /* prerequisites: in user state, lipstick running and display off */ if( system_state != MCE_SYSTEM_STATE_USER ) goto EXIT; if( lipstick_service_state != SERVICE_STATE_RUNNING ) goto EXIT; if( display_state_curr != MCE_DISPLAY_OFF ) goto EXIT; /* but not during calls, alarms, etc */ if( uiexception_type != UIEXCEPTION_TYPE_NONE ) goto EXIT; /* when lid is closed */ if( lid_sensor_filtered == COVER_CLOSED ) goto EXIT; /* or when proximity is covered */ if( proximity_sensor_effective != COVER_OPEN ) goto EXIT; /* Switch to lpm mode if the proximity sensor history matches activity * we expect to see when "the device is taken from pocket" etc */ if( tklock_lpmui_probe() ) { mce_log(LL_DEBUG, "switching to LPM UI"); /* Note: Display plugin handles MCE_DISPLAY_LPM_ON request as * MCE_DISPLAY_OFF unless lpm mode is both supported * and enabled. */ mce_datapipe_request_display_state(MCE_DISPLAY_LPM_ON); } EXIT: return; } /** LPM UI related actions that should be done before display state transition */ static void tklock_lpmui_pre_transition_actions(void) { mce_log(LL_DEBUG, "prev=%d, next=%d", display_state_curr, display_state_next); switch( display_state_next ) { case MCE_DISPLAY_LPM_ON: case MCE_DISPLAY_LPM_OFF: /* We are about to make transition to LPM state */ tklock_lpmui_set_state(true); break; case MCE_DISPLAY_OFF: switch( display_state_curr ) { case MCE_DISPLAY_ON: case MCE_DISPLAY_DIM: /* We are about to power off from ON/DIM */ /* If display is turned off via pull from top gesture * it is highly likely that the proximity sensor gets * covered -> to avoid immediate bounce back to lpm * state we need to reset proximity state history */ tklock_lpmui_reset_history(); break; default: break; } break; case MCE_DISPLAY_ON: case MCE_DISPLAY_DIM: /* We are about to make transition to ON/DIM state */ tklock_lpmui_set_state(false); break; default: case MCE_DISPLAY_UNDEF: case MCE_DISPLAY_POWER_UP: case MCE_DISPLAY_POWER_DOWN: // dontcare break; } } /* ========================================================================= * * LEGACY HW EVENT INPUT ENABLE/DISABLE STATE MACHINE * ========================================================================= */ /** Helper for dealing with enable/disable sysfs files * * @note Since nothing sensible can be done on error except reporting it, * we don't return the status * * @param output control structure for enable/disable file * @param enable TRUE enable events, FALSE disable events */ static void tklock_evctrl_set_state(output_state_t *output, bool enable) { if( !output->path ) goto EXIT; if( !mce_write_number_string_to_file(output, !enable ? 1 : 0) ) { mce_log(LL_ERR, "%s: Event status *not* modified", output->path); goto EXIT; } mce_log(LL_DEBUG, "%s: events %s", output->path, enable ? "enabled" : "disabled"); EXIT: return; } /** Disable/Enable keypad input events */ static void tklock_evctrl_set_kp_state(bool enable) { static int enabled = -1; // does not match any bool value if( !mce_keypad_sysfs_disable_output.path ) goto EXIT; if( enabled == enable ) goto EXIT; mce_log(LL_DEBUG, "%s", enable ? "enable" : "disable"); if( (enabled = enable) ) { /* Enable keypress interrupts (events will be generated by kernel) */ tklock_evctrl_set_state(&mce_keypad_sysfs_disable_output, TRUE); } else { /* Disable keypress interrupts (no events will be generated by kernel) */ tklock_evctrl_set_state(&mce_keypad_sysfs_disable_output, FALSE); } EXIT: return; } /** Disable/Enable touch screen input events */ static void tklock_evctrl_set_ts_state(bool enable) { static int enabled = -1; // does not match any bool value if( !mce_touchscreen_sysfs_disable_output.path ) goto EXIT; if( enabled == enable ) goto EXIT; mce_log(LL_DEBUG, "%s", enable ? "enable" : "disable"); if( (enabled = enable) ) { /* Enable touchscreen interrupts * (events will be generated by kernel) */ tklock_evctrl_set_state(&mce_touchscreen_sysfs_disable_output, TRUE); g_usleep(MCE_TOUCHSCREEN_CALIBRATION_DELAY); } else { /* Disable touchscreen interrupts * (no events will be generated by kernel) */ tklock_evctrl_set_state(&mce_touchscreen_sysfs_disable_output, FALSE); } EXIT: return; } /** Disable/Enable doubletap input events */ static void tklock_evctrl_set_dt_state(bool enable) { static int enabled = -1; // does not match any bool value if( !mce_touchscreen_gesture_enable_path ) goto EXIT; if( enabled == enable ) goto EXIT; mce_log(LL_DEBUG, "%s", enable ? "enable" : "disable"); if( (enabled = enable) ) { mce_write_string_to_file(mce_touchscreen_gesture_enable_path, "4"); tklock_dtcalib_start(); // NOTE: touchscreen inputs must be enabled too } else { tklock_dtcalib_stop(); mce_write_string_to_file(mce_touchscreen_gesture_enable_path, "0"); /* Disabling the double tap gesture causes recalibration */ g_usleep(MCE_TOUCHSCREEN_CALIBRATION_DELAY); } EXIT: return; } /** Process event input enable state for maemo/meego devices * * This state machine is used for maemo/meego devices (N9, N950, * N900, etc) that have separate controls for disabling/enabling * input events. * * Devices that use android style power management (Jolla) handle * this implicitly via early/late suspend. */ static void tklock_evctrl_rethink(void) { /* state variable hooks: * proximity_sensor_effective <-- tklock_datapipe_proximity_sensor_actual_cb() * display_state_curr <-- tklock_datapipe_display_state_curr_cb() * submode <-- tklock_datapipe_submode_cb() * call_state <-- tklock_datapipe_call_state_cb() */ bool enable_kp = true; bool enable_ts = true; bool enable_dt = true; /* - - - - - - - - - - - - - - - - - - - * * keypad interrupts * - - - - - - - - - - - - - - - - - - - */ /* display must be on/dim */ switch( display_state_curr ) { case MCE_DISPLAY_ON: case MCE_DISPLAY_DIM: break; default: case MCE_DISPLAY_UNDEF: case MCE_DISPLAY_OFF: case MCE_DISPLAY_LPM_OFF: case MCE_DISPLAY_LPM_ON: case MCE_DISPLAY_POWER_UP: case MCE_DISPLAY_POWER_DOWN: enable_kp = false; break; } /* If the cover is closed, don't bother */ #if 0 /* TODO: Lid cover state is tracked, but volume keys should be * disabled only if they are unlikely to be useful i.e. * depends on where physical buttons are located and * whether the cover makes pressing them impossible or not. * * In absense of such info, better to do nothing. */ if( lid_sensor_filtered == COVER_CLOSED ) { enable_kp = false; } #endif // FIXME: USERMODE only? /* Don't disable kp during call (volume keys must work) */ switch( call_state ) { case CALL_STATE_RINGING: case CALL_STATE_ACTIVE: enable_kp = true; break; default: break; } /* enable volume keys if music playing */ if( music_playback_ongoing ) enable_kp = true; /* - - - - - - - - - - - - - - - - - - - * * touchscreen interrupts * - - - - - - - - - - - - - - - - - - - */ /* display must be on/dim */ switch( display_state_curr ) { case MCE_DISPLAY_ON: case MCE_DISPLAY_DIM: break; default: case MCE_DISPLAY_UNDEF: case MCE_DISPLAY_OFF: case MCE_DISPLAY_LPM_OFF: case MCE_DISPLAY_LPM_ON: case MCE_DISPLAY_POWER_UP: case MCE_DISPLAY_POWER_DOWN: enable_ts = false; break; } // FIXME: USERMODE or ACT_DEAD with alarm? /* - - - - - - - - - - - - - - - - - - - * * doubletap interrupts * - - - - - - - - - - - - - - - - - - - */ /* display must be off */ switch( display_state_curr ) { case MCE_DISPLAY_OFF: case MCE_DISPLAY_LPM_OFF: case MCE_DISPLAY_LPM_ON: break; default: case MCE_DISPLAY_ON: case MCE_DISPLAY_DIM: case MCE_DISPLAY_UNDEF: case MCE_DISPLAY_POWER_UP: case MCE_DISPLAY_POWER_DOWN: enable_dt = false; break; } /* check if touchscreen gestures are disabled */ switch( touchscreen_gesture_enable_mode ) { case DBLTAP_ENABLE_ALWAYS: break; case DBLTAP_ENABLE_NEVER: enable_dt = false; break; default: case DBLTAP_ENABLE_NO_PROXIMITY: if( proximity_sensor_effective != COVER_OPEN ) enable_dt = false; break; } /* Finally, ensure that touchscreen interrupts are enabled * if doubletap gestures are enabled */ if( enable_dt ) { enable_ts = true; } /* - - - - - - - - - - - - - - - - - - - * * overrides * - - - - - - - - - - - - - - - - - - - */ #if 0 // FIXME: malf is not really supported yet if( submode & MCE_SUBMODE_MALF ) { enable_kp = false; enable_ts = false; enable_dt = false; } #endif /* No interaction during shutdown */ if( shutting_down ) { enable_kp = false; enable_ts = false; enable_dt = false; } /* - - - - - - - - - - - - - - - - - - - * * set updated state * - - - - - - - - - - - - - - - - - - - */ mce_log(LL_DEBUG, "kp=%d dt=%d ts=%d", enable_kp, enable_dt, enable_ts); tklock_evctrl_set_kp_state(enable_kp); tklock_evctrl_set_dt_state(enable_dt); tklock_evctrl_set_ts_state(enable_ts); /* - - - - - - - - - - - - - - - - - - - * * in case emitting of touch events can't * be controlled, we use evdev input grab * to block ui from seeing them while the * display is off * - - - - - - - - - - - - - - - - - - - */ bool grab_ts = datapipe_get_gint(touch_grab_wanted_pipe); switch( display_state_curr ) { default: case MCE_DISPLAY_OFF: case MCE_DISPLAY_POWER_DOWN: case MCE_DISPLAY_UNDEF: case MCE_DISPLAY_LPM_ON: case MCE_DISPLAY_LPM_OFF: // want grab grab_ts = true; break; case MCE_DISPLAY_POWER_UP: // keep grab state break; case MCE_DISPLAY_ON: case MCE_DISPLAY_DIM: // grab/ungrab based on policy grab_ts = !enable_ts; break; } if( !tk_input_policy_enabled ) grab_ts = false; /* Grabbing touch input is always permitted, but ungrabbing * only when proximity sensor is not covered / proximity * blocks input feature is disabled */ if( grab_ts || ( (proximity_sensor_effective == COVER_OPEN || !proximity_blocks_touch) && (lid_sensor_filtered != COVER_CLOSED) ) ) { datapipe_exec_full(&touch_grab_wanted_pipe, GINT_TO_POINTER(grab_ts)); } /* - - - - - - - - - - - - - - - - - - - * * in case emitting of keypad events can't * be controlled, we use evdev input grab * to block ui from seeing them while the * display is off * - - - - - - - - - - - - - - - - - - - */ bool grab_kp = !enable_kp; switch( volkey_policy ) { case VOLKEY_POLICY_MEDIA_ONLY: if( !music_playback_ongoing ) grab_kp = true; break; default: break; } if( !tk_input_policy_enabled ) grab_kp = false; datapipe_exec_full(&keypad_grab_wanted_pipe, GINT_TO_POINTER(grab_kp)); return; } /* ========================================================================= * * LEGACY HW DOUBLE TAP CALIBRATION * ========================================================================= */ /** Do double tap recalibration on heartbeat */ static gboolean tklock_dtcalib_on_heartbeat = FALSE; /** Double tap recalibration delays */ static const guint tklock_dtcalib_delays[] = { 2, 4, 8, 16, 30 }; /** Double tap recalibration index */ static guint tklock_dtcalib_index = 0; /** Double tap recalibration timeout identifier */ static guint tklock_dtcalib_timeout_id = 0; /** Kick the double tap recalibrating sysfs file unconditionally */ static void tklock_dtcalib_now(void) { mce_log(LL_DEBUG, "Recalibrating double tap"); mce_write_string_to_file(mce_touchscreen_calibration_control_path, "1"); } /** Kick the double tap recalibrating sysfs file from heartbeat */ static void tklock_dtcalib_from_heartbeat(void) { if( tklock_dtcalib_on_heartbeat ) { mce_log(LL_DEBUG, "double tap calibration @ heartbeat"); tklock_dtcalib_now(); } } /** Callback for doubletap recalibration timer * * @param data Not used. * * @return Always FALSE for remove event source */ static gboolean tklock_dtcalib_cb(gpointer data) { (void)data; if( !tklock_dtcalib_timeout_id ) goto EXIT; tklock_dtcalib_timeout_id = 0; mce_log(LL_DEBUG, "double tap calibration @ timer"); tklock_dtcalib_now(); /* If at last delay, start recalibrating on DSME heartbeat */ if( tklock_dtcalib_index == G_N_ELEMENTS(tklock_dtcalib_delays) ) { tklock_dtcalib_on_heartbeat = TRUE; goto EXIT; } /* Otherwise use next delay */ tklock_dtcalib_timeout_id = g_timeout_add_seconds(tklock_dtcalib_delays[tklock_dtcalib_index++], tklock_dtcalib_cb, NULL); EXIT: return FALSE; } /** Cancel doubletap recalibration timeouts */ static void tklock_dtcalib_stop(void) { /* stop timer based kicking */ if( tklock_dtcalib_timeout_id ) g_source_remove(tklock_dtcalib_timeout_id), tklock_dtcalib_timeout_id = 0; /* stop heartbeat based kicking */ tklock_dtcalib_on_heartbeat = FALSE; } /** Setup doubletap recalibration timeouts */ static void tklock_dtcalib_start(void) { if( !mce_touchscreen_calibration_control_path ) goto EXIT; tklock_dtcalib_stop(); tklock_dtcalib_index = 0; tklock_dtcalib_timeout_id = g_timeout_add_seconds(tklock_dtcalib_delays[tklock_dtcalib_index++], tklock_dtcalib_cb, NULL); EXIT: return; } /* ========================================================================= * * DYNAMIC_SETTINGS * ========================================================================= */ static void tklock_setting_sanitize_lid_open_actions(void) { switch( tklock_lid_open_actions ) { case LID_OPEN_ACTION_DISABLED: case LID_OPEN_ACTION_UNBLANK: case LID_OPEN_ACTION_TKUNLOCK: break; default: mce_log(LL_WARN, "Lid open has invalid policy: %d; " "using default", tklock_lid_open_actions); tklock_lid_open_actions = MCE_DEFAULT_TK_LID_OPEN_ACTIONS; break; } } static void tklock_setting_sanitize_lid_close_actions(void) { switch( tklock_lid_close_actions ) { case LID_CLOSE_ACTION_DISABLED: case LID_CLOSE_ACTION_BLANK: case LID_CLOSE_ACTION_TKLOCK: break; default: mce_log(LL_WARN, "Lid close has invalid policy: %d; " "using default", tklock_lid_close_actions); tklock_lid_close_actions = MCE_DEFAULT_TK_LID_CLOSE_ACTIONS; break; } } static void tklock_setting_sanitize_kbd_open_trigger(void) { switch( tklock_kbd_open_trigger ) { case KBD_OPEN_TRIGGER_NEVER: case KBD_OPEN_TRIGGER_ALWAYS: case KBD_OPEN_TRIGGER_NO_PROXIMITY: break; default: mce_log(LL_WARN, "Invalid kbd open trigger: %d; using default", tklock_kbd_open_trigger); tklock_kbd_open_trigger = MCE_DEFAULT_TK_KBD_OPEN_TRIGGER; break; } } static void tklock_setting_sanitize_kbd_open_actions(void) { switch( tklock_kbd_open_actions ) { case LID_OPEN_ACTION_DISABLED: case LID_OPEN_ACTION_UNBLANK: case LID_OPEN_ACTION_TKUNLOCK: break; default: mce_log(LL_WARN, "Invalid kbd open actions: %d; using default", tklock_kbd_open_actions); tklock_kbd_open_actions = MCE_DEFAULT_TK_KBD_OPEN_ACTIONS; break; } } static void tklock_setting_sanitize_kbd_close_trigger(void) { switch( tklock_kbd_close_trigger ) { case KBD_CLOSE_TRIGGER_NEVER: case KBD_CLOSE_TRIGGER_ALWAYS: case KBD_CLOSE_TRIGGER_AFTER_OPEN: break; default: mce_log(LL_WARN, "Invalid kbd close trigger: %d; using default", tklock_kbd_close_trigger); tklock_kbd_close_trigger = MCE_DEFAULT_TK_KBD_CLOSE_TRIGGER; break; } } static void tklock_setting_sanitize_kbd_close_actions(void) { switch( tklock_kbd_close_actions ) { case LID_CLOSE_ACTION_DISABLED: case LID_CLOSE_ACTION_BLANK: case LID_CLOSE_ACTION_TKLOCK: break; default: mce_log(LL_WARN, "Invalid kbd close actions: %d; using default", tklock_kbd_close_actions); tklock_kbd_close_actions = MCE_DEFAULT_TK_KBD_CLOSE_ACTIONS; break; } } /** GConf callback for touchscreen/keypad lock related settings * * @param gcc Unused * @param id Connection ID from gconf_client_notify_add() * @param entry The modified GConf entry * @param data Unused */ static void tklock_setting_cb(GConfClient *const gcc, const guint id, GConfEntry *const entry, gpointer const data) { (void)gcc; (void)data; (void)id; 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 == tk_autolock_enabled_setting_id ) { tk_autolock_enabled = gconf_value_get_bool(gcv) ? 1 : 0; tklock_autolock_rethink(); } else if( id == tk_input_policy_enabled_setting_id ) { gboolean old = tk_input_policy_enabled; tk_input_policy_enabled = gconf_value_get_bool(gcv) ? 1 : 0; if( tk_input_policy_enabled != old ) { mce_log(LL_NOTICE, "input grabbing %s", tk_input_policy_enabled ? "allowed" : "denied"); tklock_evctrl_rethink(); } } else if( id == lid_sensor_enabled_setting_id ) { lid_sensor_enabled = gconf_value_get_bool(gcv) ? 1 : 0; tklock_lidfilter_rethink_lid_state(); } else if( id == als_enabled_setting_id ) { als_enabled = gconf_value_get_bool(gcv); tklock_lidfilter_rethink_lid_state(); } else if( id == filter_lid_with_als_setting_id ) { filter_lid_with_als = gconf_value_get_bool(gcv); tklock_lidfilter_rethink_lid_state(); } else if( id == filter_lid_als_limit_setting_id ) { filter_lid_als_limit = gconf_value_get_int(gcv); tklock_lidfilter_rethink_lid_state(); } else if( id == lockscreen_anim_enabled_setting_id ) { lockscreen_anim_enabled= gconf_value_get_bool(gcv); } else if( id == tklock_autolock_delay_setting_id ) { gint old = tklock_autolock_delay; tklock_autolock_delay = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "tklock_autolock_delay: %d -> %d", old, tklock_autolock_delay); // Note: takes effect the next time display turns off } else if( id == proximity_blocks_touch_setting_id ) { proximity_blocks_touch = gconf_value_get_bool(gcv) ? 1 : 0; tklock_evctrl_rethink(); } else if( id == volkey_policy_setting_id ) { volkey_policy = gconf_value_get_int(gcv); tklock_evctrl_rethink(); } else if( id == tklock_lid_open_actions_setting_id ) { tklock_lid_open_actions = gconf_value_get_int(gcv); tklock_setting_sanitize_lid_open_actions(); tklock_evctrl_rethink(); } else if( id == tklock_lid_close_actions_setting_id ) { tklock_lid_close_actions = gconf_value_get_int(gcv); tklock_setting_sanitize_lid_close_actions(); tklock_evctrl_rethink(); } else if( id == tklock_kbd_open_trigger_setting_id ) { tklock_kbd_open_trigger = gconf_value_get_int(gcv); tklock_setting_sanitize_kbd_open_trigger(); } else if( id == tklock_kbd_open_actions_setting_id ) { tklock_kbd_open_actions = gconf_value_get_int(gcv); tklock_setting_sanitize_kbd_open_actions(); } else if( id == tklock_kbd_close_trigger_setting_id ) { tklock_kbd_close_trigger = gconf_value_get_int(gcv); tklock_setting_sanitize_kbd_close_trigger(); } else if( id == tklock_kbd_close_actions_setting_id ) { tklock_kbd_close_actions = gconf_value_get_int(gcv); tklock_setting_sanitize_kbd_close_actions(); } else if( id == touchscreen_gesture_enable_mode_setting_id ) { gint old = touchscreen_gesture_enable_mode; touchscreen_gesture_enable_mode = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "touchscreen_gesture_enable_mode: %d -> %d", old, touchscreen_gesture_enable_mode); tklock_evctrl_rethink(); } else if( id == tklock_lpmui_triggering_setting_id ) { gint old = tklock_lpmui_triggering; tklock_lpmui_triggering = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "tklock_lpmui_triggering: %d -> %d", old, tklock_lpmui_triggering); } else if( id == tklock_devicelock_in_lockscreen_setting_id ) { gboolean old = tklock_devicelock_in_lockscreen; tklock_devicelock_in_lockscreen = gconf_value_get_bool(gcv); mce_log(LL_NOTICE, "tklock_devicelock_in_lockscreen: %d -> %d", old, tklock_devicelock_in_lockscreen); } else if( id == exception_length_call_in_setting_id ) { gint old = exception_length_call_in; exception_length_call_in = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "exception_length_call_in: %d -> %d", old, exception_length_call_in); } else if( id == exception_length_call_out_setting_id ) { gint old = exception_length_call_out; exception_length_call_out = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "exception_length_call_out: %d -> %d", old, exception_length_call_out); } else if( id == exception_length_alarm_setting_id ) { gint old = exception_length_alarm; exception_length_alarm = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "exception_length_alarm: %d -> %d", old, exception_length_alarm); } else if( id == exception_length_usb_connect_setting_id ) { gint old = exception_length_usb_connect; exception_length_usb_connect = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "exception_length_usb_connect: %d -> %d", old, exception_length_usb_connect); } else if( id == exception_length_usb_dialog_setting_id ) { gint old = exception_length_usb_dialog; exception_length_usb_dialog = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "exception_length_usb_dialog: %d -> %d", old, exception_length_usb_dialog); } else if( id == exception_length_charger_setting_id ) { gint old = exception_length_charger; exception_length_charger = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "exception_length_charger: %d -> %d", old, exception_length_charger); } else if( id == exception_length_battery_setting_id ) { gint old = exception_length_battery; exception_length_battery = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "exception_length_battery: %d -> %d", old, exception_length_battery); } else if( id == exception_length_jack_in_setting_id ) { gint old = exception_length_jack_in; exception_length_jack_in = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "exception_length_jack_in: %d -> %d", old, exception_length_jack_in); } else if( id == exception_length_jack_out_setting_id ) { gint old = exception_length_jack_out; exception_length_jack_out = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "exception_length_jack_out: %d -> %d", old, exception_length_jack_out); } else if( id == exception_length_camera_setting_id ) { gint old = exception_length_camera; exception_length_camera = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "exception_length_camera: %d -> %d", old, exception_length_camera); } else if( id == exception_length_volume_setting_id ) { gint old = exception_length_volume; exception_length_volume = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "exception_length_volume: %d -> %d", old, exception_length_volume); } else if( id == exception_length_activity_setting_id ) { gint old = exception_length_activity; exception_length_activity = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "exception_length_activity: %d -> %d", old, exception_length_activity); } else if( id == tklock_proximity_delay_default_setting_id ) { gint old = tklock_proximity_delay_default; tklock_proximity_delay_default = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "proximity_delay_default: %d -> %d", old, tklock_proximity_delay_default); } else if( id == tklock_proximity_delay_incall_setting_id ) { gint old = tklock_proximity_delay_incall; tklock_proximity_delay_incall = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "proximity_delay_incall: %d -> %d", old, tklock_proximity_delay_incall); } else { mce_log(LL_WARN, "Spurious GConf value received; confused!"); } EXIT: return; } /** Get intial setting values and start tracking changes */ static void tklock_setting_init(void) { /* Touchscreen/keypad autolock enabled */ mce_setting_track_bool(MCE_SETTING_TK_AUTOLOCK_ENABLED, &tk_autolock_enabled, MCE_DEFAULT_TK_AUTOLOCK_ENABLED, tklock_setting_cb, &tk_autolock_enabled_setting_id); /* Grabbing input devices allowed */ mce_setting_track_bool(MCE_SETTING_TK_INPUT_POLICY_ENABLED, &tk_input_policy_enabled, MCE_DEFAULT_TK_INPUT_POLICY_ENABLED, tklock_setting_cb, &tk_input_policy_enabled_setting_id); /* Touchscreen/keypad autolock delay */ mce_setting_track_int(MCE_SETTING_TK_AUTOLOCK_DELAY, &tklock_autolock_delay, MCE_DEFAULT_TK_AUTOLOCK_DELAY, tklock_setting_cb, &tklock_autolock_delay_setting_id); /* Volume key input policy */ mce_setting_track_int(MCE_SETTING_TK_VOLKEY_POLICY, &volkey_policy, MCE_DEFAULT_TK_VOLKEY_POLICY, tklock_setting_cb, &volkey_policy_setting_id); /* Lid sensor open policy */ mce_setting_track_int(MCE_SETTING_TK_LID_OPEN_ACTIONS, &tklock_lid_open_actions, MCE_DEFAULT_TK_LID_OPEN_ACTIONS, tklock_setting_cb, &tklock_lid_open_actions_setting_id); tklock_setting_sanitize_lid_open_actions(); /* Lid sensor close policy */ mce_setting_track_int(MCE_SETTING_TK_LID_CLOSE_ACTIONS, &tklock_lid_close_actions, MCE_DEFAULT_TK_LID_CLOSE_ACTIONS, tklock_setting_cb, &tklock_lid_close_actions_setting_id); tklock_setting_sanitize_lid_close_actions(); /* Kbd slide open policy */ mce_setting_track_int(MCE_SETTING_TK_KBD_OPEN_TRIGGER, &tklock_kbd_open_trigger, MCE_DEFAULT_TK_KBD_OPEN_TRIGGER, tklock_setting_cb, &tklock_kbd_open_trigger_setting_id); tklock_setting_sanitize_kbd_open_trigger(); mce_setting_track_int(MCE_SETTING_TK_KBD_OPEN_ACTIONS, &tklock_kbd_open_actions, MCE_DEFAULT_TK_KBD_OPEN_ACTIONS, tklock_setting_cb, &tklock_kbd_open_actions_setting_id); tklock_setting_sanitize_kbd_open_actions(); /* Kbd slide close policy */ mce_setting_track_int(MCE_SETTING_TK_KBD_CLOSE_TRIGGER, &tklock_kbd_close_trigger, MCE_DEFAULT_TK_KBD_CLOSE_TRIGGER, tklock_setting_cb, &tklock_kbd_close_trigger_setting_id); tklock_setting_sanitize_kbd_close_trigger(); mce_setting_track_int(MCE_SETTING_TK_KBD_CLOSE_ACTIONS, &tklock_kbd_close_actions, MCE_DEFAULT_TK_KBD_CLOSE_ACTIONS, tklock_setting_cb, &tklock_kbd_close_actions_setting_id); tklock_setting_sanitize_kbd_close_actions(); /** Touchscreen double tap gesture mode */ mce_setting_track_int(MCE_SETTING_DOUBLETAP_MODE, &touchscreen_gesture_enable_mode, MCE_DEFAULT_DOUBLETAP_MODE, tklock_setting_cb, &touchscreen_gesture_enable_mode_setting_id); /* Bitmap of automatic lpm triggering modes */ mce_setting_track_int(MCE_SETTING_TK_LPMUI_TRIGGERING, &tklock_lpmui_triggering, MCE_DEFAULT_TK_LPMUI_TRIGGERING, tklock_setting_cb, &tklock_lpmui_triggering_setting_id); /* Proximity can block touch input */ mce_setting_track_bool(MCE_SETTING_TK_PROXIMITY_BLOCKS_TOUCH, &proximity_blocks_touch, MCE_DEFAULT_TK_PROXIMITY_BLOCKS_TOUCH, tklock_setting_cb, &proximity_blocks_touch_setting_id); /* Devicelock is in lockscreen */ mce_setting_track_bool(MCE_SETTING_TK_DEVICELOCK_IN_LOCKSCREEN, &tklock_devicelock_in_lockscreen, MCE_DEFAULT_TK_DEVICELOCK_IN_LOCKSCREEN, tklock_setting_cb, &tklock_devicelock_in_lockscreen_setting_id); /* Touchscreen/keypad autolock enabled */ mce_setting_track_bool(MCE_SETTING_TK_LID_SENSOR_ENABLED, &lid_sensor_enabled, MCE_DEFAULT_TK_LID_SENSOR_ENABLED, tklock_setting_cb, &lid_sensor_enabled_setting_id); mce_setting_track_bool(MCE_SETTING_DISPLAY_ALS_ENABLED, &als_enabled, MCE_DEFAULT_DISPLAY_ALS_ENABLED, tklock_setting_cb, &als_enabled_setting_id); mce_setting_track_bool(MCE_SETTING_TK_FILTER_LID_WITH_ALS, &filter_lid_with_als, MCE_DEFAULT_TK_FILTER_LID_WITH_ALS, tklock_setting_cb, &filter_lid_with_als_setting_id); mce_setting_track_int(MCE_SETTING_TK_FILTER_LID_ALS_LIMIT, &filter_lid_als_limit, MCE_DEFAULT_TK_FILTER_LID_ALS_LIMIT, tklock_setting_cb, &filter_lid_als_limit_setting_id); /* Display on exception lengths */ mce_setting_track_int(MCE_SETTING_TK_EXCEPT_LEN_CALL_IN, &exception_length_call_in, MCE_DEFAULT_TK_EXCEPT_LEN_CALL_IN, tklock_setting_cb, &exception_length_call_in_setting_id); mce_setting_track_int(MCE_SETTING_TK_EXCEPT_LEN_CALL_OUT, &exception_length_call_out, MCE_DEFAULT_TK_EXCEPT_LEN_CALL_OUT, tklock_setting_cb, &exception_length_call_out_setting_id); mce_setting_track_int(MCE_SETTING_TK_EXCEPT_LEN_ALARM, &exception_length_alarm, MCE_DEFAULT_TK_EXCEPT_LEN_ALARM, tklock_setting_cb, &exception_length_alarm_setting_id); mce_setting_track_int(MCE_SETTING_TK_EXCEPT_LEN_USB_CONNECT, &exception_length_usb_connect, MCE_DEFAULT_TK_EXCEPT_LEN_USB_CONNECT, tklock_setting_cb, &exception_length_usb_connect_setting_id); mce_setting_track_int(MCE_SETTING_TK_EXCEPT_LEN_USB_DIALOG, &exception_length_usb_dialog, MCE_DEFAULT_TK_EXCEPT_LEN_USB_DIALOG, tklock_setting_cb, &exception_length_usb_dialog_setting_id); mce_setting_track_int(MCE_SETTING_TK_EXCEPT_LEN_CHARGER, &exception_length_charger, MCE_DEFAULT_TK_EXCEPT_LEN_CHARGER, tklock_setting_cb, &exception_length_charger_setting_id); mce_setting_track_int(MCE_SETTING_TK_EXCEPT_LEN_BATTERY, &exception_length_battery, MCE_DEFAULT_TK_EXCEPT_LEN_BATTERY, tklock_setting_cb, &exception_length_battery_setting_id); mce_setting_track_int(MCE_SETTING_TK_EXCEPT_LEN_JACK_IN, &exception_length_jack_in, MCE_DEFAULT_TK_EXCEPT_LEN_JACK_IN, tklock_setting_cb, &exception_length_jack_in_setting_id); mce_setting_track_int(MCE_SETTING_TK_EXCEPT_LEN_JACK_OUT, &exception_length_jack_out, MCE_DEFAULT_TK_EXCEPT_LEN_JACK_OUT, tklock_setting_cb, &exception_length_jack_out_setting_id); mce_setting_track_int(MCE_SETTING_TK_EXCEPT_LEN_CAMERA, &exception_length_camera, MCE_DEFAULT_TK_EXCEPT_LEN_CAMERA, tklock_setting_cb, &exception_length_camera_setting_id); mce_setting_track_int(MCE_SETTING_TK_EXCEPT_LEN_VOLUME, &exception_length_volume, MCE_DEFAULT_TK_EXCEPT_LEN_VOLUME, tklock_setting_cb, &exception_length_volume_setting_id); mce_setting_track_int(MCE_SETTING_TK_EXCEPT_LEN_ACTIVITY, &exception_length_activity, MCE_DEFAULT_TK_EXCEPT_LEN_ACTIVITY, tklock_setting_cb, &exception_length_activity_setting_id); mce_setting_track_bool(MCE_SETTING_TK_LOCKSCREEN_ANIM_ENABLED, &lockscreen_anim_enabled, MCE_DEFAULT_TK_LOCKSCREEN_ANIM_ENABLED, tklock_setting_cb, &lockscreen_anim_enabled_setting_id); /* Delays for proximity sensor uncover handling */ mce_setting_track_int(MCE_SETTING_TK_PROXIMITY_DELAY_DEFAULT, &tklock_proximity_delay_default, MCE_DEFAULT_TK_PROXIMITY_DELAY_DEFAULT, tklock_setting_cb, &tklock_proximity_delay_default_setting_id); mce_setting_track_int(MCE_SETTING_TK_PROXIMITY_DELAY_INCALL, &tklock_proximity_delay_incall, MCE_DEFAULT_TK_PROXIMITY_DELAY_INCALL, tklock_setting_cb, &tklock_proximity_delay_incall_setting_id); } /** Stop tracking setting changes */ static void tklock_setting_quit(void) { mce_setting_notifier_remove(volkey_policy_setting_id), volkey_policy_setting_id = 0; mce_setting_notifier_remove(tklock_lid_open_actions_setting_id), tklock_lid_open_actions_setting_id = 0; mce_setting_notifier_remove(tklock_lid_close_actions_setting_id), tklock_lid_close_actions_setting_id = 0; mce_setting_notifier_remove(tklock_kbd_open_trigger_setting_id), tklock_kbd_open_trigger_setting_id = 0; mce_setting_notifier_remove(tklock_kbd_open_actions_setting_id), tklock_kbd_open_actions_setting_id = 0; mce_setting_notifier_remove(tklock_kbd_close_trigger_setting_id), tklock_kbd_close_trigger_setting_id = 0; mce_setting_notifier_remove(tklock_kbd_close_actions_setting_id), tklock_kbd_close_actions_setting_id = 0; mce_setting_notifier_remove(tk_autolock_enabled_setting_id), tk_autolock_enabled_setting_id = 0; mce_setting_notifier_remove(tk_input_policy_enabled_setting_id), tk_input_policy_enabled_setting_id = 0; mce_setting_notifier_remove(tklock_autolock_delay_setting_id), tklock_autolock_delay_setting_id = 0; mce_setting_notifier_remove(touchscreen_gesture_enable_mode_setting_id), touchscreen_gesture_enable_mode_setting_id = 0; mce_setting_notifier_remove(tklock_lpmui_triggering_setting_id), tklock_lpmui_triggering_setting_id = 0; mce_setting_notifier_remove(proximity_blocks_touch_setting_id), proximity_blocks_touch_setting_id = 0; mce_setting_notifier_remove(tklock_devicelock_in_lockscreen_setting_id), tklock_devicelock_in_lockscreen_setting_id = 0; mce_setting_notifier_remove(lid_sensor_enabled_setting_id), lid_sensor_enabled_setting_id = 0; mce_setting_notifier_remove(als_enabled_setting_id), als_enabled_setting_id = 0; mce_setting_notifier_remove(filter_lid_with_als_setting_id), filter_lid_with_als_setting_id = 0; mce_setting_notifier_remove(filter_lid_als_limit_setting_id), filter_lid_als_limit_setting_id = 0; mce_setting_notifier_remove(exception_length_call_in_setting_id), exception_length_call_in_setting_id = 0; mce_setting_notifier_remove(exception_length_call_out_setting_id), exception_length_call_out_setting_id = 0; mce_setting_notifier_remove(exception_length_alarm_setting_id), exception_length_alarm_setting_id = 0; mce_setting_notifier_remove(exception_length_usb_connect_setting_id), exception_length_usb_connect_setting_id = 0; mce_setting_notifier_remove(exception_length_usb_dialog_setting_id), exception_length_usb_dialog_setting_id = 0; mce_setting_notifier_remove(exception_length_charger_setting_id), exception_length_charger_setting_id = 0; mce_setting_notifier_remove(exception_length_battery_setting_id), exception_length_battery_setting_id = 0; mce_setting_notifier_remove(exception_length_jack_in_setting_id), exception_length_jack_in_setting_id = 0; mce_setting_notifier_remove(exception_length_jack_out_setting_id), exception_length_jack_out_setting_id = 0; mce_setting_notifier_remove(exception_length_camera_setting_id), exception_length_camera_setting_id = 0; mce_setting_notifier_remove(exception_length_volume_setting_id), exception_length_volume_setting_id = 0; mce_setting_notifier_remove(exception_length_activity_setting_id), exception_length_activity_setting_id = 0; mce_setting_notifier_remove(lockscreen_anim_enabled_setting_id), lockscreen_anim_enabled_setting_id = 0; } /* ========================================================================= * * SYSFS PROBING * ========================================================================= */ /** Init event control files */ static void tklock_sysfs_probe(void) { /* touchscreen event control interface */ if (g_access(MCE_RX51_KEYBOARD_SYSFS_DISABLE_PATH, W_OK) == 0) { mce_keypad_sysfs_disable_output.path = MCE_RX51_KEYBOARD_SYSFS_DISABLE_PATH; } else if (g_access(MCE_RX44_KEYBOARD_SYSFS_DISABLE_PATH, W_OK) == 0) { mce_keypad_sysfs_disable_output.path = MCE_RX44_KEYBOARD_SYSFS_DISABLE_PATH; } else if (g_access(MCE_KEYPAD_SYSFS_DISABLE_PATH, W_OK) == 0) { mce_keypad_sysfs_disable_output.path = MCE_KEYPAD_SYSFS_DISABLE_PATH; } else { mce_log(LL_INFO, "No touchscreen event control interface available"); } /* keypress event control interface */ if (g_access(MCE_RM680_TOUCHSCREEN_SYSFS_DISABLE_PATH, W_OK) == 0) { mce_touchscreen_sysfs_disable_output.path = MCE_RM680_TOUCHSCREEN_SYSFS_DISABLE_PATH; } else if (g_access(MCE_RX44_TOUCHSCREEN_SYSFS_DISABLE_PATH_KERNEL2637, W_OK) == 0) { mce_touchscreen_sysfs_disable_output.path = MCE_RX44_TOUCHSCREEN_SYSFS_DISABLE_PATH_KERNEL2637; } else if (g_access(MCE_RX44_TOUCHSCREEN_SYSFS_DISABLE_PATH, W_OK) == 0) { mce_touchscreen_sysfs_disable_output.path = MCE_RX44_TOUCHSCREEN_SYSFS_DISABLE_PATH; } else { mce_log(LL_INFO, "No keypress event control interface available"); } /* touchscreen gesture control interface */ if (g_access(MCE_RM680_DOUBLETAP_SYSFS_PATH, W_OK) == 0) { mce_touchscreen_gesture_enable_path = MCE_RM680_DOUBLETAP_SYSFS_PATH; } else { mce_log(LL_INFO, "No touchscreen gesture control interface available"); } /* touchscreen calibration control interface */ if (g_access(MCE_RM680_TOUCHSCREEN_CALIBRATION_PATH, W_OK) == 0) { mce_touchscreen_calibration_control_path = MCE_RM680_TOUCHSCREEN_CALIBRATION_PATH; } else { mce_log(LL_INFO, "No touchscreen calibration control interface " "available"); } } /* ========================================================================= * * DBUS IPC WITH SYSTEMUI * ========================================================================= */ static void tklock_ui_eat_event(void) { /* FIXME: get rid of this function and all explicit event eater ipc */ const char *cb_service = MCE_SERVICE; const char *cb_path = MCE_REQUEST_PATH; const char *cb_interface = MCE_REQUEST_IF; const char *cb_method = MCE_TKLOCK_CB_REQ; dbus_bool_t flicker_key = has_flicker_key; dbus_uint32_t mode = TKLOCK_ONEINPUT; dbus_bool_t silent = TRUE; mce_log(LL_DEBUG, "sending tklock ui event eater"); /* org.nemomobile.lipstick.screenlock.tklock_open */ dbus_send(SYSTEMUI_SERVICE, SYSTEMUI_REQUEST_PATH, SYSTEMUI_REQUEST_IF, SYSTEMUI_TKLOCK_OPEN_REQ, NULL, DBUS_TYPE_STRING, &cb_service, DBUS_TYPE_STRING, &cb_path, DBUS_TYPE_STRING, &cb_interface, DBUS_TYPE_STRING, &cb_method, DBUS_TYPE_UINT32, &mode, DBUS_TYPE_BOOLEAN, &silent, DBUS_TYPE_BOOLEAN, &flicker_key, DBUS_TYPE_INVALID); } static void tklock_ui_open(void) { const char *cb_service = MCE_SERVICE; const char *cb_path = MCE_REQUEST_PATH; const char *cb_interface = MCE_REQUEST_IF; const char *cb_method = MCE_TKLOCK_CB_REQ; dbus_bool_t flicker_key = has_flicker_key; dbus_uint32_t mode = TKLOCK_ENABLE_VISUAL; dbus_bool_t silent = TRUE; mce_log(LL_DEBUG, "sending tklock ui open"); /* org.nemomobile.lipstick.screenlock.tklock_open */ dbus_send(SYSTEMUI_SERVICE, SYSTEMUI_REQUEST_PATH, SYSTEMUI_REQUEST_IF, SYSTEMUI_TKLOCK_OPEN_REQ, NULL, DBUS_TYPE_STRING, &cb_service, DBUS_TYPE_STRING, &cb_path, DBUS_TYPE_STRING, &cb_interface, DBUS_TYPE_STRING, &cb_method, DBUS_TYPE_UINT32, &mode, DBUS_TYPE_BOOLEAN, &silent, DBUS_TYPE_BOOLEAN, &flicker_key, DBUS_TYPE_INVALID); } static void tklock_ui_close(void) { dbus_bool_t silent = TRUE; mce_log(LL_DEBUG, "sending tklock ui close"); /* org.nemomobile.lipstick.screenlock.tklock_close */ dbus_send(SYSTEMUI_SERVICE, SYSTEMUI_REQUEST_PATH, SYSTEMUI_REQUEST_IF, SYSTEMUI_TKLOCK_CLOSE_REQ, NULL, DBUS_TYPE_BOOLEAN, &silent, DBUS_TYPE_INVALID); } static guint tklock_ui_notify_end_id = 0; static guint tklock_ui_notify_beg_id = 0; static void tklock_ui_send_tklock_signal(void) { bool current = tklock_ui_is_enabled(); if( tklock_ui_notified == current ) goto EXIT; tklock_ui_notified = current; /* do lipstick specific ipc */ if( lipstick_service_state == SERVICE_STATE_RUNNING ) { if( current ) tklock_ui_open(); else tklock_ui_close(); } /* broadcast signal */ tklock_dbus_send_tklock_mode(0); EXIT: return; } static void tklock_ui_notify_rethink_wakelock(void) { static bool have_lock = false; bool need_lock = (tklock_ui_notify_beg_id || tklock_ui_notify_end_id); if( have_lock == need_lock ) goto EXIT; mce_log(LL_DEBUG, "ui notify wakelock: %s", need_lock ? "OBTAIN" : "RELEASE"); if( (have_lock = need_lock) ) { wakelock_lock("mce_tklock_notify", -1); } else wakelock_unlock("mce_tklock_notify"); EXIT: return; } static bool tklock_ui_notify_must_be_delayed(void) { bool delay = false; /* We do not want to send tklock changes during display power * off sequence as those might trigger lockscreen related * animations at UI side */ if( display_state_curr == MCE_DISPLAY_POWER_DOWN ) { /* Powering down the display for any reason */ delay = true; } else if( display_state_curr != display_state_next ) { switch( display_state_curr ) { case MCE_DISPLAY_LPM_ON: /* Making transition from lpm state. In order not * to confuse device lock ui, finish the display * state transition before acting on tklock state. */ delay = true; break; default: break; } switch( display_state_next ) { case MCE_DISPLAY_OFF: case MCE_DISPLAY_LPM_OFF: /* Making transition to a blanked display state */ delay = true; break; default: break; } } return delay; } static gboolean tklock_ui_notify_end_cb(gpointer data) { (void) data; if( !tklock_ui_notify_end_id ) goto EXIT; tklock_ui_notify_end_id = 0; EXIT: tklock_ui_notify_rethink_wakelock(); return FALSE; } static gboolean tklock_ui_notify_beg_cb(gpointer data) { (void) data; if( !tklock_ui_notify_beg_id ) goto EXIT; tklock_ui_notify_beg_id = 0; if( tklock_ui_notify_must_be_delayed() ) goto EXIT; /* Broadcast tklock state 1st */ tklock_ui_send_tklock_signal(); /* Deal with possibly ending lpm state */ tklock_ui_send_lpm_signal(); /* Deal with redirection of tkunlock -> show device lock prompt */ if( tklock_devicelock_want_to_unlock ) { if( tklock_ui_is_enabled() && display_state_next == MCE_DISPLAY_ON ) { mce_log(LL_DEBUG, "request: show device lock query"); tklock_ui_show_device_unlock(); } else { mce_log(LL_WARN, "skipped: show device lock query"); } tklock_devicelock_want_to_unlock = false; } /* give ui a chance to see the signal */ if( tklock_ui_notify_end_id ) g_source_remove(tklock_ui_notify_end_id); tklock_ui_notify_end_id = g_timeout_add(2000, tklock_ui_notify_end_cb, 0); EXIT: tklock_ui_notify_rethink_wakelock(); return FALSE; } static void tklock_ui_notify_cancel(void) { if( tklock_ui_notify_end_id ) { g_source_remove(tklock_ui_notify_end_id), tklock_ui_notify_end_id = 0; } if( tklock_ui_notify_beg_id ) { g_source_remove(tklock_ui_notify_beg_id), tklock_ui_notify_beg_id = 0; } tklock_ui_notify_rethink_wakelock(); } static void tklock_ui_notify_schdule(void) { if( tklock_ui_notify_end_id ) { g_source_remove(tklock_ui_notify_end_id), tklock_ui_notify_end_id = 0; } if( tklock_ui_notify_must_be_delayed() ) goto EXIT; if( !tklock_ui_notify_beg_id ) { tklock_ui_notify_beg_id = g_idle_add(tklock_ui_notify_beg_cb, 0); } EXIT: tklock_ui_notify_rethink_wakelock(); } /** Timer for synchronizing tklock ui state -> submode tklock bit */ static guint tklock_ui_sync_id = 0; /** Callback for synchronizing tklock_ui -> submode tklock bit */ static gboolean tklock_ui_sync_cb(gpointer aptr) { (void)aptr; tklock_ui_sync_id = 0; mce_log(LL_DEBUG, "tklock sync triggered"); bool enabled = tklock_ui_is_enabled(); if( tklock_datapipe_in_tklock_submode() != enabled ) tklock_datapipe_set_tklock_submode(enabled); return G_SOURCE_REMOVE; } static bool tklock_ui_is_enabled(void) { return tklock_ui_enabled_pvt; } static void tklock_ui_set_enabled(bool enable) { /* See also tklock_datapipe_set_tklock_submode() */ /* Note: As long as lipstick process is running, mce must * not attempt forced tklock removal as it can lead * to tklock state ringing if/when lipstick happens * to require tklock to be set. */ /* Filter request based on device state */ /* When there is no UI to lock, allowing tklock to * be set can only cause problems */ if( enable && lipstick_service_state != SERVICE_STATE_RUNNING ) { mce_log(LL_INFO, "deny tklock; lipstick not running"); enable = false; goto EXIT; } /* If device lock is handled in lockscreen, we must not * allow *removing* of tklock (=move away from lockscreen) * while device lock is still active. */ if( !enable && tklock_devicelock_in_lockscreen && devicelock_state == DEVICELOCK_STATE_LOCKED ) { mce_log(LL_DEVEL, "deny tkunlock; show device lock query"); tklock_devicelock_want_to_unlock = true; enable = true; goto EXIT; } /* Do not allow unlocking while lid sensor is enabled and covered */ if( !enable && lid_sensor_filtered == COVER_CLOSED && !enable ) { mce_log(LL_WARN, "deny tkunlock; lid sensor is covered"); enable = true; goto EXIT; } /* Request accepted as-is */ EXIT: /* Check and handle state change */ if( tklock_ui_enabled_pvt != enable ) { tklock_ui_enabled_pvt = enable; mce_log(LL_DEBUG, "tklock_ui_enabled: %s", tklock_ui_enabled_pvt ? "TRUE" : "FALSE"); } /* Schedule notification attempt even if there is no change, * so that ui side is not left thinking that a tklock request * it made was accepted. */ tklock_ui_notify_schdule(); /* Sync to submode in any case */ if( !tklock_ui_sync_id ) { mce_log(LL_DEBUG, "tklock sync scheduled"); tklock_ui_sync_id = g_idle_add(tklock_ui_sync_cb, 0); } } /** Handle reply to device lock state query */ static void tklock_ui_get_devicelock_cb(DBusPendingCall *pc, void *aptr) { (void)aptr; DBusMessage *rsp = 0; DBusError err = DBUS_ERROR_INIT; dbus_int32_t val = 0; if( !(rsp = dbus_pending_call_steal_reply(pc)) ) goto EXIT; if( dbus_set_error_from_message(&err, rsp) ) { mce_log(LL_ERR, "%s: %s", err.name, err.message); goto EXIT; } if( !dbus_message_get_args(rsp, &err, DBUS_TYPE_INT32, &val, DBUS_TYPE_INVALID) ) { mce_log(LL_ERR, "%s: %s", err.name, err.message); goto EXIT; } mce_log(LL_INFO, "device lock status reply: state=%s", devicelock_state_repr(val)); tklock_datapipe_set_devicelock_state(val); EXIT: if( rsp ) dbus_message_unref(rsp); dbus_error_free(&err); } /** Initiate asynchronous device lock state query */ static void tklock_ui_get_devicelock(void) { mce_log(LL_DEBUG, "query device lock status"); dbus_send(DEVICELOCK_SERVICE, DEVICELOCK_REQUEST_PATH, DEVICELOCK_REQUEST_IF, "state", tklock_ui_get_devicelock_cb, DBUS_TYPE_INVALID); } /** Broadcast LPM UI state over D-Bus */ static void tklock_ui_send_lpm_signal(void) { if( tklock_lpmui_state_signaled == tklock_lpmui_state_wanted ) goto EXIT; tklock_lpmui_state_signaled = tklock_lpmui_state_wanted; bool enabled = (tklock_lpmui_state_wanted > 0); /* Do lipstick specific ipc 1st */ if( lipstick_service_state == SERVICE_STATE_RUNNING ) { if( enabled ) tklock_ui_enable_lpm(); else tklock_ui_disable_lpm(); } /* then send the signal */ const char *sig = MCE_LPM_UI_MODE_SIG; const char *arg = enabled ? MCE_LPM_UI_ENABLED : MCE_LPM_UI_DISABLED; mce_log(LL_DEVEL, "sending dbus signal: %s %s", sig, arg); dbus_send(0, MCE_SIGNAL_PATH, MCE_SIGNAL_IF, sig, 0, DBUS_TYPE_STRING, &arg, DBUS_TYPE_INVALID); EXIT: return; } /** Tell lipstick that lpm ui mode is enabled */ static void tklock_ui_enable_lpm(void) { /* Use the N9 legacy D-Bus method call */ const char *cb_service = MCE_SERVICE; const char *cb_path = MCE_REQUEST_PATH; const char *cb_interface = MCE_REQUEST_IF; const char *cb_method = MCE_TKLOCK_CB_REQ; dbus_bool_t flicker_key = has_flicker_key; dbus_uint32_t mode = TKLOCK_ENABLE_LPM_UI; dbus_bool_t silent = TRUE; mce_log(LL_DEBUG, "sending tklock ui lpm enable"); /* org.nemomobile.lipstick.screenlock.tklock_open */ dbus_send(SYSTEMUI_SERVICE, SYSTEMUI_REQUEST_PATH, SYSTEMUI_REQUEST_IF, SYSTEMUI_TKLOCK_OPEN_REQ, NULL, DBUS_TYPE_STRING, &cb_service, DBUS_TYPE_STRING, &cb_path, DBUS_TYPE_STRING, &cb_interface, DBUS_TYPE_STRING, &cb_method, DBUS_TYPE_UINT32, &mode, DBUS_TYPE_BOOLEAN, &silent, DBUS_TYPE_BOOLEAN, &flicker_key, DBUS_TYPE_INVALID); } /** Tell lipstick that lpm ui mode is disabled */ static void tklock_ui_disable_lpm(void) { // FIXME: we do not have method call for cancelling lpm state } /** Tell lipstick that device unlock prompt should be shown */ static void tklock_ui_show_device_unlock(void) { /* Re-use the signal that lipstick already uses for selecting between * plain lockscreen and showing the device unlock view in the context * of configurable power-button actions. * * The naming of the signal is a bit unfortunate, since * 1) it is now used for other things besides dealing with power key * 2) having the tkunlock redirection available means that it would * not be needed at all in the power key handler ... */ const char *sig = MCE_POWER_BUTTON_TRIGGER; const char *arg = "double-power-key"; dbus_send(0, MCE_SIGNAL_PATH, MCE_SIGNAL_IF, sig, 0, DBUS_TYPE_STRING, &arg, DBUS_TYPE_INVALID); } /* ========================================================================= * * DBUS MESSAGE HANDLERS * ========================================================================= */ /** Send the blanking policy state * * @param req A method call message to be replied, or * NULL to broadcast a policy change signal */ static void tklock_dbus_send_display_blanking_policy(DBusMessage *const req) { DBusMessage *rsp = 0; if( req ) rsp = dbus_new_method_reply(req); else rsp = dbus_new_signal(MCE_SIGNAL_PATH, MCE_SIGNAL_IF, MCE_BLANKING_POLICY_SIG); if( !rsp ) goto EXIT; const char *arg = uiexception_type_to_dbus(uiexception_type); mce_log(LL_DEBUG, "send display blanking policy %s: %s", req ? "reply" : "signal", arg); if( !dbus_message_append_args(rsp, DBUS_TYPE_STRING, &arg, DBUS_TYPE_INVALID) ) goto EXIT; dbus_send_message(rsp), rsp = 0; EXIT: if( rsp ) dbus_message_unref(rsp); } /** D-Bus callback for the get blakng policy state method call * * @param msg The D-Bus message * * @return TRUE */ static gboolean tklock_dbus_display_blanking_policy_get_cb(DBusMessage *const msg) { mce_log(LL_DEVEL, "Received blanking policy get from %s", mce_dbus_get_message_sender_ident(msg)); tklock_dbus_send_display_blanking_policy(msg); return TRUE; } /** Send the keyboard slide open/closed state * * @param req A method call message to be replied, or * NULL to broadcast a keypad state signal */ static void tklock_dbus_send_keyboard_slide_state(DBusMessage *const req) { DBusMessage *rsp = 0; if( req ) rsp = dbus_new_method_reply(req); else rsp = dbus_new_signal(MCE_SIGNAL_PATH, MCE_SIGNAL_IF, MCE_SLIDING_KEYBOARD_STATE_SIG); if( !rsp ) goto EXIT; const char *arg = MCE_SLIDING_KEYBOARD_UNDEF; switch( keyboard_slide_output_state ) { case COVER_OPEN: arg = MCE_SLIDING_KEYBOARD_OPEN; break; case COVER_CLOSED: arg = MCE_SLIDING_KEYBOARD_CLOSED; break; default: break; } mce_log(LL_DEBUG, "send keyboard slide state %s: %s", req ? "reply" : "signal", arg); if( !dbus_message_append_args(rsp, DBUS_TYPE_STRING, &arg, DBUS_TYPE_INVALID) ) goto EXIT; dbus_send_message(rsp), rsp = 0; EXIT: if( rsp ) dbus_message_unref(rsp); } /** D-Bus callback for the get keyboard slide state method call * * @param msg The D-Bus message * * @return TRUE */ static gboolean tklock_dbus_keyboard_slide_state_get_req_cb(DBusMessage *const msg) { mce_log(LL_DEVEL, "Received keyboard slide state get request from %s", mce_dbus_get_message_sender_ident(msg)); tklock_dbus_send_keyboard_slide_state(msg); return TRUE; } /** Send the keyboard available state * * @param req A method call message to be replied, or * NULL to broadcast a keyboard available state signal */ static void tklock_dbus_send_keyboard_available_state(DBusMessage *const req) { DBusMessage *rsp = 0; if( req ) rsp = dbus_new_method_reply(req); else rsp = dbus_new_signal(MCE_SIGNAL_PATH, MCE_SIGNAL_IF, MCE_HARDWARE_KEYBOARD_STATE_SIG); if( !rsp ) goto EXIT; const char *arg = MCE_HARDWARE_KEYBOARD_UNDEF; switch( keyboard_available_state ) { case COVER_OPEN: arg = MCE_HARDWARE_KEYBOARD_AVAILABLE; break; case COVER_CLOSED: arg = MCE_HARDWARE_KEYBOARD_NOT_AVAILABLE; break; default: break; } mce_log(LL_DEBUG, "send keyboard available state %s: %s", req ? "reply" : "signal", arg); if( !dbus_message_append_args(rsp, DBUS_TYPE_STRING, &arg, DBUS_TYPE_INVALID) ) goto EXIT; dbus_send_message(rsp), rsp = 0; EXIT: if( rsp ) dbus_message_unref(rsp); } /** D-Bus callback for the get keyboard available state method call * * @param msg The D-Bus message * * @return TRUE */ static gboolean tklock_dbus_keyboard_available_state_get_req_cb(DBusMessage *const msg) { mce_log(LL_DEVEL, "Received keyboard available state get request from %s", mce_dbus_get_message_sender_ident(msg)); tklock_dbus_send_keyboard_available_state(msg); return TRUE; } /** * Send the touchscreen/keypad lock mode * * @param method_call A DBusMessage to reply to; * pass NULL to send a tklock mode signal instead * @return TRUE on success, FALSE on failure */ static gboolean tklock_dbus_send_tklock_mode(DBusMessage *const method_call) { gboolean status = FALSE; DBusMessage *msg = NULL; /* Note: Events on D-Bus must be based on tklock ui state, * not submode tklock bit. */ const char *mode = (tklock_ui_is_enabled() ? MCE_TK_LOCKED : MCE_TK_UNLOCKED); /* If method_call is set, send a reply. Otherwise, send a signal. */ if( method_call ) { msg = dbus_new_method_reply(method_call); mce_log(LL_DEBUG, "send tklock mode reply: %s", mode); } else { msg = dbus_new_signal(MCE_SIGNAL_PATH, MCE_SIGNAL_IF, MCE_TKLOCK_MODE_SIG); mce_log(LL_DEVEL, "send tklock mode signal: %s", mode); } if( !dbus_message_append_args(msg, DBUS_TYPE_STRING, &mode, DBUS_TYPE_INVALID) ) { mce_log(LL_ERR, "Failed to append %sargument to D-Bus message " "for %s.%s", method_call ? "reply " : "", method_call ? MCE_REQUEST_IF : MCE_SIGNAL_IF, method_call ? MCE_TKLOCK_MODE_GET : MCE_TKLOCK_MODE_SIG); goto EXIT; } /* Send the message */ status = dbus_send_message(msg), msg = 0; EXIT: if( msg ) dbus_message_unref(msg); return status; } /** * D-Bus callback for the get tklock mode method call * * @param msg The D-Bus message * @return TRUE on success, FALSE on failure */ static gboolean tklock_dbus_mode_get_req_cb(DBusMessage *const msg) { gboolean status = FALSE; mce_log(LL_DEVEL, "Received tklock mode get request from %s", mce_dbus_get_message_sender_ident(msg)); /* Try to send a reply that contains the current tklock mode */ if( !tklock_dbus_send_tklock_mode(msg) ) goto EXIT; status = TRUE; EXIT: return status; } /** Apply allow/deny policy for TKLock requests received over D-Bus * * Basically locking is always allowed, but unlocking only when * display already is / is making transition to powered up state. * * @param state requested state * * @returns allowed state */ static tklock_request_t tklock_dbus_sanitize_requested_mode(tklock_request_t state) { /* Translate toggle requests to something we can evaluate */ if( state == TKLOCK_REQUEST_TOGGLE ) state = tklock_ui_is_enabled() ? TKLOCK_REQUEST_OFF : TKLOCK_REQUEST_ON; switch( state ) { default: case TKLOCK_REQUEST_UNDEF: case TKLOCK_REQUEST_TOGGLE: break; case TKLOCK_REQUEST_OFF: case TKLOCK_REQUEST_OFF_DELAYED: case TKLOCK_REQUEST_OFF_PROXIMITY: state = TKLOCK_REQUEST_OFF; switch( display_state_next ) { case MCE_DISPLAY_ON: case MCE_DISPLAY_DIM: break; default: if( tklock_ui_is_enabled() ) { mce_log(LL_WARN, "tkunlock denied due to display=%s", display_state_repr(display_state_next)); state = TKLOCK_REQUEST_ON; } break; } goto EXIT; case TKLOCK_REQUEST_ON: case TKLOCK_REQUEST_ON_DIMMED: case TKLOCK_REQUEST_ON_PROXIMITY: case TKLOCK_REQUEST_ON_DELAYED: state = TKLOCK_REQUEST_ON; break; } EXIT: return state; } /** * D-Bus callback for the tklock mode change method call * * @param msg The D-Bus message * @return TRUE on success, FALSE on failure */ static gboolean tklock_dbus_mode_change_req_cb(DBusMessage *const msg) { dbus_bool_t no_reply = dbus_message_get_no_reply(msg); const char *mode = NULL; gboolean status = FALSE; DBusError error = DBUS_ERROR_INIT; if( !dbus_message_get_args(msg, &error, DBUS_TYPE_STRING, &mode, DBUS_TYPE_INVALID) ) { mce_log(LL_ERR, "Failed to get argument from %s.%s: %s", MCE_REQUEST_IF, MCE_TKLOCK_MODE_CHANGE_REQ, error.message); goto EXIT; } mce_log(LL_DEVEL, "Received tklock mode change request '%s' from %s", mode, mce_dbus_get_message_sender_ident(msg)); int state = TKLOCK_REQUEST_UNDEF; if (!strcmp(MCE_TK_LOCKED, mode)) state = TKLOCK_REQUEST_ON; else if (!strcmp(MCE_TK_LOCKED_DIM, mode)) state = TKLOCK_REQUEST_ON_DIMMED; else if (!strcmp(MCE_TK_LOCKED_DELAY, mode)) state = TKLOCK_REQUEST_ON_DELAYED; else if (!strcmp(MCE_TK_UNLOCKED, mode)) state = TKLOCK_REQUEST_OFF; else mce_log(LL_WARN, "Received an invalid tklock mode; ignoring"); mce_log(LL_DEBUG, "mode: %s/%d", mode, state); if( state != TKLOCK_REQUEST_UNDEF ) { tklock_ui_notified = -1; state = tklock_dbus_sanitize_requested_mode(state); tklock_datapipe_tklock_request_cb(GINT_TO_POINTER(state)); } if( no_reply ) status = TRUE; else { DBusMessage *reply = dbus_new_method_reply(msg); status = dbus_send_message(reply); } EXIT: dbus_error_free(&error); return status; } /** D-Bus callback for handling interaction expected -state changed signals * * @param msg The D-Bus message * * @return TRUE */ static gboolean tklock_dbus_interaction_expected_cb(DBusMessage *const msg) { DBusError err = DBUS_ERROR_INIT; dbus_bool_t arg = false; if( !msg ) goto EXIT; if( !dbus_message_get_args(msg, &err, DBUS_TYPE_BOOLEAN, &arg, DBUS_TYPE_INVALID) ) { mce_log(LL_ERR, "Failed to parse interaction expected signal: %s: %s", err.name, err.message); goto EXIT; } mce_log(LL_DEBUG, "received interaction expected signal: state=%d", arg); tklock_datapipe_update_interaction_expected(arg); EXIT: dbus_error_free(&err); return TRUE; } /** * D-Bus callback from SystemUI touchscreen/keypad lock * * @todo the calls to disable_tklock/open_tklock_ui need error handling * * @param msg D-Bus message with the lock status * @return TRUE on success, FALSE on failure */ static gboolean tklock_dbus_systemui_callback_cb(DBusMessage *const msg) { dbus_int32_t result = INT_MAX; gboolean status = FALSE; DBusError error = DBUS_ERROR_INIT; if( !dbus_message_get_args(msg, &error, DBUS_TYPE_INT32, &result, DBUS_TYPE_INVALID) ) { mce_log(LL_ERR, "Failed to get argument from %s.%s: %s", MCE_REQUEST_IF, MCE_TKLOCK_CB_REQ, error.message); goto EXIT; } mce_log(LL_DEVEL, "tklock callback value: %s, from %s", tklock_status_repr(result), mce_dbus_get_message_sender_ident(msg)); tklock_request_t state = TKLOCK_REQUEST_OFF; switch( result ) { case TKLOCK_UNLOCK: tklock_ui_notified = -1; state = tklock_dbus_sanitize_requested_mode(state); tklock_datapipe_tklock_request_cb(GINT_TO_POINTER(state)); break; default: case TKLOCK_CLOSED: break; } status = TRUE; EXIT: dbus_error_free(&error); return status; } /** D-Bus callback for notification begin request * * @param msg D-Bus message with the notification name and duration * * @return TRUE */ static gboolean tklock_dbus_notification_beg_cb(DBusMessage *const msg) { DBusError err = DBUS_ERROR_INIT; const char *name = 0; dbus_int32_t dur = 0; dbus_int32_t add = 0; const char *from = dbus_message_get_sender(msg); if( !from ) goto EXIT; if( !dbus_message_get_args(msg, &err, DBUS_TYPE_STRING,&name, DBUS_TYPE_INT32, &dur, DBUS_TYPE_INT32, &add, DBUS_TYPE_INVALID) ) { mce_log(LL_ERR, "Failed to get arguments: %s: %s", err.name, err.message); goto EXIT; } mce_log(LL_CRUCIAL, "notification begin from %s", mce_dbus_get_message_sender_ident(msg)); mce_tklock_begin_notification(from, name, dur, add); EXIT: /* Send dummy reply if requested */ if( !dbus_message_get_no_reply(msg) ) dbus_send_message(dbus_new_method_reply(msg)); dbus_error_free(&err); return TRUE; } /** D-Bus callback for notification end request * * @param msg D-Bus message with the notification name and duration * * @return TRUE */ static gboolean tklock_dbus_notification_end_cb(DBusMessage *const msg) { DBusError err = DBUS_ERROR_INIT; const char *name = 0; dbus_int32_t dur = 0; const char *from = dbus_message_get_sender(msg); if( !from ) goto EXIT; if( !dbus_message_get_args(msg, &err, DBUS_TYPE_STRING,&name, DBUS_TYPE_INT32, &dur, DBUS_TYPE_INVALID) ) { mce_log(LL_ERR, "Failed to get arguments: %s: %s", err.name, err.message); goto EXIT; } mce_log(LL_CRUCIAL, "notification end from %s", mce_dbus_get_message_sender_ident(msg)); mce_tklock_end_notification(from, name, dur); EXIT: /* Send dummy reply if requested */ if( !dbus_message_get_no_reply(msg) ) dbus_send_message(dbus_new_method_reply(msg)); dbus_error_free(&err); return TRUE; } /** D-Bus callback for handling device lock state changed signals * * @param msg The D-Bus message * * @return TRUE */ static gboolean tklock_dbus_devicelock_changed_cb(DBusMessage *const msg) { DBusError err = DBUS_ERROR_INIT; dbus_int32_t val = 0; if( !msg ) goto EXIT; if( !dbus_message_get_args(msg, &err, DBUS_TYPE_INT32, &val, DBUS_TYPE_INVALID) ) { mce_log(LL_ERR, "Failed to parse device lock signal: %s: %s", err.name, err.message); goto EXIT; } mce_log(LL_DEBUG, "received device lock signal: state=%s", devicelock_state_repr(val)); tklock_datapipe_set_devicelock_state(val); EXIT: dbus_error_free(&err); return TRUE; } /** Array of dbus message handlers */ static mce_dbus_handler_t tklock_dbus_handlers[] = { /* signals - inbound */ { .interface = "org.nemomobile.lipstick.devicelock", .name = "stateChanged", .rules = "path='/devicelock'", .type = DBUS_MESSAGE_TYPE_SIGNAL, .callback = tklock_dbus_devicelock_changed_cb, }, { .interface = "org.nemomobile.lipstick.screenlock", .name = "interaction_expected", .rules = "path='/screenlock'", .type = DBUS_MESSAGE_TYPE_SIGNAL, .callback = tklock_dbus_interaction_expected_cb, }, /* signals - outbound (for Introspect purposes only) */ { .interface = MCE_SIGNAL_IF, .name = MCE_TKLOCK_MODE_SIG, .type = DBUS_MESSAGE_TYPE_SIGNAL, .args = " \n" }, { .interface = MCE_SIGNAL_IF, .name = MCE_LPM_UI_MODE_SIG, .type = DBUS_MESSAGE_TYPE_SIGNAL, .args = " \n" }, { .interface = MCE_SIGNAL_IF, .name = MCE_SLIDING_KEYBOARD_STATE_SIG, .type = DBUS_MESSAGE_TYPE_SIGNAL, .args = " \n" }, { .interface = MCE_SIGNAL_IF, .name = MCE_HARDWARE_KEYBOARD_STATE_SIG, .type = DBUS_MESSAGE_TYPE_SIGNAL, .args = " \n" }, { .interface = MCE_SIGNAL_IF, .name = MCE_BLANKING_POLICY_SIG, .type = DBUS_MESSAGE_TYPE_SIGNAL, .args = " \n" }, /* method calls */ { .interface = MCE_REQUEST_IF, .name = MCE_TKLOCK_MODE_GET, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = tklock_dbus_mode_get_req_cb, .args = " \n" }, { .interface = MCE_REQUEST_IF, .name = MCE_TKLOCK_MODE_CHANGE_REQ, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = tklock_dbus_mode_change_req_cb, .args = " \n" }, { .interface = MCE_REQUEST_IF, .name = MCE_TKLOCK_CB_REQ, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = tklock_dbus_systemui_callback_cb, .args = " \n" }, { .interface = MCE_REQUEST_IF, .name = MCE_NOTIFICATION_BEGIN_REQ, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = tklock_dbus_notification_beg_cb, .args = " \n" " \n" " \n" }, { .interface = MCE_REQUEST_IF, .name = MCE_NOTIFICATION_END_REQ, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = tklock_dbus_notification_end_cb, .args = " \n" " \n" }, { .interface = MCE_REQUEST_IF, .name = MCE_SLIDING_KEYBOARD_STATE_GET, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = tklock_dbus_keyboard_slide_state_get_req_cb, .args = " \n" }, { .interface = MCE_REQUEST_IF, .name = MCE_HARDWARE_KEYBOARD_STATE_GET, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = tklock_dbus_keyboard_available_state_get_req_cb, .args = " \n" }, { .interface = MCE_REQUEST_IF, .name = MCE_BLANKING_POLICY_GET, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = tklock_dbus_display_blanking_policy_get_cb, .args = " \n" }, /* sentinel */ { .interface = 0 } }; /** Add dbus handlers */ static void mce_tklock_init_dbus(void) { mce_dbus_handler_register_array(tklock_dbus_handlers); } /** Remove dbus handlers */ static void mce_tklock_quit_dbus(void) { mce_dbus_handler_unregister_array(tklock_dbus_handlers); } /* ========================================================================= * * NOTIFICATION_SLOTS * ========================================================================= */ static void tklock_notif_slot_init(tklock_notif_slot_t *self) { self->ns_owner = 0; self->ns_name = 0; self->ns_until = 0; self->ns_renew = 0; } static void tklock_notif_slot_free(tklock_notif_slot_t *self) { gchar *owner = tklock_notif_slot_steal_owner(self); if( self->ns_name ) mce_log(LL_DEVEL, "notification '%s' removed", self->ns_name); g_free(self->ns_name), self->ns_name = 0; self->ns_until = 0; self->ns_renew = 0; tklock_notif_remove_owner_monitor(owner); g_free(owner); } static void tklock_notif_slot_set(tklock_notif_slot_t *self, const char *owner, const char *name, int64_t until, int64_t renew) { tklock_notif_slot_free(self); self->ns_owner = owner ? g_strdup(owner) : 0; self->ns_name = g_strdup(name); self->ns_until = until; self->ns_renew = renew; if( self->ns_name ) mce_log(LL_DEVEL, "notification '%s' added", self->ns_name); tklock_notif_add_owner_monitor(owner); } static bool tklock_notif_slot_is_free(const tklock_notif_slot_t *self) { return self->ns_name == 0; } static bool tklock_notif_slot_has_name(const tklock_notif_slot_t *self, const char *name) { return self->ns_name && !strcmp(self->ns_name, name); } static bool tklock_notif_slot_validate(tklock_notif_slot_t *self, int64_t now) { if( now <= self->ns_until ) return true; tklock_notif_slot_free(self); return false; } static bool tklock_notif_slot_renew(tklock_notif_slot_t *self, int64_t now) { int64_t tmo = now + self->ns_renew; if( tmo <= self->ns_until ) return false; self->ns_until = tmo; return true; } static inline bool eq(const char *s1, const char *s2) { return (s1 && s2) ? !strcmp(s1, s2) : (s1 == s2); } static bool tklock_notif_slot_has_owner(const tklock_notif_slot_t *self, const char *owner) { return eq(self->ns_owner, owner); } static gchar * tklock_notif_slot_steal_owner(tklock_notif_slot_t *self) { gchar *owner = self->ns_owner; self->ns_owner = 0; return owner; } /* ========================================================================= * * NOTIFICATION_API * ========================================================================= */ static tklock_notif_state_t tklock_notif_state; static void tklock_notif_init(void) { tklock_notif_state.tn_linger = MIN_TICK; tklock_notif_state.tn_autostop_id = 0; tklock_notif_state.tn_monitor_list = 0; for( size_t i = 0; i < TKLOCK_NOTIF_SLOTS; ++i ) { tklock_notif_slot_t *slot = tklock_notif_state.tn_slot + i; tklock_notif_slot_init(slot); } } static void tklock_notif_quit(void) { tklock_notif_cancel_autostop(); for( size_t i = 0; i < TKLOCK_NOTIF_SLOTS; ++i ) { tklock_notif_slot_t *slot = tklock_notif_state.tn_slot + i; tklock_notif_slot_free(slot); } /* Make sure the above loop removed all the monitoring callbacks */ if( tklock_notif_state.tn_monitor_list ) mce_log(LL_WARN, "entries left in owner monitor list"); mce_dbus_owner_monitor_remove_all(&tklock_notif_state.tn_monitor_list); } static gboolean tklock_notif_autostop_cb(gpointer aptr) { (void)aptr; if( !tklock_notif_state.tn_autostop_id ) goto EXIT; mce_log(LL_DEBUG, "triggered"); tklock_notif_state.tn_autostop_id = 0; tklock_notif_update_state(); EXIT: return FALSE; } static void tklock_notif_cancel_autostop(void) { if( tklock_notif_state.tn_autostop_id ) { mce_log(LL_DEBUG, "cancelled"); g_source_remove(tklock_notif_state.tn_autostop_id), tklock_notif_state.tn_autostop_id = 0; } } static void tklock_notif_schedule_autostop(gint delay) { tklock_notif_cancel_autostop(); mce_log(LL_DEBUG, "scheduled in %d ms", delay); tklock_notif_state.tn_autostop_id = g_timeout_add(delay, tklock_notif_autostop_cb, 0); } static void tklock_notif_update_state(void) { int64_t now = mce_lib_get_boot_tick(); int64_t tmo = MAX_TICK; for( size_t i = 0; i < TKLOCK_NOTIF_SLOTS; ++i ) { tklock_notif_slot_t *slot = tklock_notif_state.tn_slot + i; if( tklock_notif_slot_is_free(slot) ) continue; if( !tklock_notif_slot_validate(slot, now) ) continue; if( tmo > slot->ns_until ) tmo = slot->ns_until; } tklock_notif_cancel_autostop(); if( tmo < MAX_TICK ) { tklock_notif_schedule_autostop((gint)(tmo - now)); tklock_uiexception_begin(UIEXCEPTION_TYPE_NOTIF, 0); tklock_uiexception_rethink(); } else { if( (tmo = tklock_notif_state.tn_linger - now) < 0 ) tmo = 0; tklock_uiexception_end(UIEXCEPTION_TYPE_NOTIF, tmo); tklock_uiexception_rethink(); } } static void tklock_notif_extend_by_renew(void) { int64_t now = mce_lib_get_boot_tick(); bool changed = false; for( size_t i = 0; i < TKLOCK_NOTIF_SLOTS; ++i ) { tklock_notif_slot_t *slot = tklock_notif_state.tn_slot + i; if( tklock_notif_slot_is_free(slot) ) continue; if( !tklock_notif_slot_validate(slot, now) ) changed = true; else if( tklock_notif_slot_renew(slot, now) ) changed = true; } if( changed ) tklock_notif_update_state(); } static void tklock_notif_vacate_slot(const char *owner, const char *name, int64_t linger) { for( size_t i = 0; i < TKLOCK_NOTIF_SLOTS; ++i ) { tklock_notif_slot_t *slot = tklock_notif_state.tn_slot + i; if( !tklock_notif_slot_has_name(slot, name) ) continue; if( !tklock_notif_slot_has_owner(slot, owner) ) continue; tklock_notif_slot_free(slot); int64_t now = mce_lib_get_boot_tick(); int64_t tmo = now + linger; if( tklock_notif_state.tn_linger < tmo ) tklock_notif_state.tn_linger = tmo; tklock_notif_update_state(); goto EXIT; } mce_log(LL_DEBUG, "attempt to end non-existing notification"); EXIT: return; } static void tklock_notif_reserve_slot(const char *owner, const char *name, int64_t length, int64_t renew) { int64_t now = mce_lib_get_boot_tick(); int64_t tmo = now + length; /* first check if slot is already reserved */ for( size_t i = 0; i < TKLOCK_NOTIF_SLOTS; ++i ) { tklock_notif_slot_t *slot = tklock_notif_state.tn_slot + i; if( !tklock_notif_slot_has_name(slot, name) ) continue; tklock_notif_slot_set(slot, owner, name, tmo, renew); tklock_notif_update_state(); goto EXIT; } /* then try to find unused slot */ for( size_t i = 0; i < TKLOCK_NOTIF_SLOTS; ++i ) { tklock_notif_slot_t *slot = tklock_notif_state.tn_slot + i; if( !tklock_notif_slot_is_free(slot) ) continue; tklock_notif_slot_set(slot, owner, name, tmo, renew); tklock_notif_update_state(); goto EXIT; } mce_log(LL_WARN, "too many concurrent notifications"); EXIT: return; } static void tklock_notif_vacate_slots_from(const char *owner) { bool changed = false; for( size_t i = 0; i < TKLOCK_NOTIF_SLOTS; ++i ) { tklock_notif_slot_t *slot = tklock_notif_state.tn_slot + i; if( tklock_notif_slot_is_free(slot) ) continue; if( !tklock_notif_slot_has_owner(slot, owner) ) continue; tklock_notif_slot_free(slot); changed = true; } if( changed ) tklock_notif_update_state(); } static size_t tklock_notif_count_slots_from(const char *owner) { size_t count = 0; for( size_t i = 0; i < TKLOCK_NOTIF_SLOTS; ++i ) { tklock_notif_slot_t *slot = tklock_notif_state.tn_slot + i; if( tklock_notif_slot_has_owner(slot, owner) ) ++count; } return count; } static gboolean tklock_notif_owner_dropped_cb(DBusMessage *const msg) { const char *name = 0; const char *prev = 0; const char *curr = 0; DBusError err = DBUS_ERROR_INIT; if( !dbus_message_get_args(msg, &err, DBUS_TYPE_STRING, &name, DBUS_TYPE_STRING, &prev, DBUS_TYPE_STRING, &curr, DBUS_TYPE_INVALID) ) { mce_log(LL_ERR, "failed to get args: %s: %s", err.name, err.message); goto EXIT; } if( !*curr ) tklock_notif_vacate_slots_from(name); EXIT: dbus_error_free(&err); return TRUE; } static void tklock_notif_add_owner_monitor(const char *owner) { if( !owner ) return; if( tklock_notif_count_slots_from(owner) != 1 ) return; /* first slot added */ mce_log(LL_DEBUG, "adding dbus monitor for: %s" ,owner); mce_dbus_owner_monitor_add(owner, tklock_notif_owner_dropped_cb, &tklock_notif_state.tn_monitor_list, TKLOCK_NOTIF_SLOTS); } static void tklock_notif_remove_owner_monitor(const char *owner) { if( !owner ) return; if( tklock_notif_count_slots_from(owner) != 0 ) return; /* last slot removed */ mce_log(LL_DEBUG, "removing dbus monitor for: %s" ,owner); mce_dbus_owner_monitor_remove(owner, &tklock_notif_state.tn_monitor_list); } /** Interface for registering notification state * * @param name assumed unique notification identifier * @param length minimum length of notification [ms] * @param renew extend length on user input [ms] */ static void mce_tklock_begin_notification(const char *owner, const char *name, int64_t length, int64_t renew) { /* Ignore zero length notifications */ if( length <= 0 ) goto EXIT; /* cap length to [1,30] second range */ if( length > 30000 ) length = 30000; else if( length < 1000 ) length = 1000; /* cap renew to [0,5] second range, negative means use default */ if( renew > 5000 ) renew = 5000; else if( renew < 0 ) renew = exception_length_activity; mce_log(LL_DEBUG, "name: %s, length: %d, renew: %d", name, (int)length, (int)renew); tklock_notif_reserve_slot(owner, name, length, renew); EXIT: return; } /** Interface for removing notification state * * @param name assumed unique notification identifier * @param linger duration to keep display on [ms] */ static void mce_tklock_end_notification(const char *owner, const char *name, int64_t linger) { /* cap linger to [0, 10] second range */ if( linger > 10000 ) linger = 10000; else if( linger < 0 ) linger = 0; mce_log(LL_DEBUG, "name: %s, linger: %d", name, (int)linger); tklock_notif_vacate_slot(owner, name, linger); } /* ========================================================================= * * MODULE LOAD/UNLOAD * ========================================================================= */ /** * Init function for the touchscreen/keypad lock component * * @return TRUE on success, FALSE on failure */ gboolean mce_tklock_init(void) { gboolean status = FALSE; /* initialize notification book keeping */ tklock_notif_init(); /* initialize proximity history to sane state */ tklock_lpmui_reset_history(); /* paths must be probed 1st, the results are used * to validate configuration and settings */ tklock_sysfs_probe(); /* get dynamic config, install change monitors */ tklock_setting_init(); tklock_autolock_init(); /* Set initial lid_sensor_is_working_pipe value * before installing datapipe handlers */ tklock_lidsensor_init(); /* attach to internal state variables */ tklock_datapipe_init(); /* set up dbus message handlers */ mce_tklock_init_dbus(); /* Make sure lpm state gets initialized & broadcast */ tklock_lpmui_set_state(false); /* Broadcast initial blanking policy */ tklock_dbus_send_display_blanking_policy(0); /* Evaluate initial lid sensor state */ tklock_lidfilter_rethink_lid_state(); status = TRUE; return status; } /** * Exit function for the touchscreen/keypad lock component */ void mce_tklock_exit(void) { /* remove all handlers */ mce_tklock_quit_dbus(); tklock_datapipe_quit(); tklock_setting_quit(); /* cancel all timers */ tklock_autolock_disable(); tklock_proxlock_disable(); tklock_uiexception_cancel(); tklock_dtcalib_stop(); tklock_datapipe_proximity_uncover_cancel(); tklock_notif_quit(); tklock_ui_notify_cancel(); tklock_autolock_quit(); if( tklock_ui_sync_id ) { g_source_remove(tklock_ui_sync_id), tklock_ui_sync_id = 0; } common_on_proximity_cancel(MODULE_NAME, 0, 0); // FIXME: check that final state is sane return; } /** Perform display powerup under faked abnormal blanking policy * * @param to_state display state to wake up to */ void mce_tklock_unblank(display_state_t to_state) { if( display_state_next == to_state) goto EXIT; if( !lockscreen_anim_enabled ) { /* Disable lockscreen animations by invoking a faked * abnormal display blanking policy for the duration * of the display power up. */ tklock_uiexception_begin(UIEXCEPTION_TYPE_NOANIM, 0); } mce_datapipe_request_display_state(to_state); EXIT: return; }