d1b097940e3b9369824e644df644b455658ade41
[platform/core/system/deviced.git] / src / display / plugin-common / display-lock.c
1 /*
2  * deviced
3  *
4  * Copyright (c) 2020 Samsung Electronics Co., Ltd.
5  *
6  * Licensed under the Apache License, Version 2.0 (the License);
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18
19 /**
20  * @file        display-lock.c
21  * @brief       Manage each state's lock. Lock blocks state transition by timeout.
22  *
23  */
24 #include "shared/common.h"
25 #include "device-interface.h"
26 #include "poll.h"
27 //#include "display.h"
28 #include "display-signal.h"
29 #include "lock-detector.h"
30 #include "display-lock.h"
31 #include "display-panel.h"
32 #include "display-config.h"
33 #include "display-state-transition.h"
34 #include "shared/log-macro.h"
35 #include "shared/apps.h"
36
37 #define METHOD_APP_STATUS       "CheckAppStatus"
38 #define PID_MAX                 6
39 #define LOCK_TIME_WARNING       60      /* 60 seconds */
40
41 #define no_transition_timer()    (get_transition_timer() == 0)
42 #define no_foreground_lock(st)   (check_lock_state(st) == false)
43
44 static GList *cond_head[S_END];
45 static int trans_condition;
46 static bool custom_holdkey_block = false;
47
48 bool check_lock_state(int state)
49 {
50         GList *elem;
51         PmLockNode *t;
52
53         SYS_G_LIST_FOREACH(cond_head[state], elem, t) {
54                 if (t->app_background == false)
55                         return true;
56         }
57
58         return false;
59 }
60
61 bool pmlock_get_lock_state(enum state_t state)
62 {
63         return (bool)SYS_G_LIST_LENGTH(cond_head[state]);
64 }
65
66 enum state_t power_lock_type_to_pmlock(power_lock_e power_lock_type)
67 {
68         switch(power_lock_type) {
69         case POWER_LOCK_CPU:
70                 return S_LCDOFF;
71         case POWER_LOCK_DISPLAY:
72                 return S_LCDON;
73         case POWER_LOCK_DISPLAY_DIM:
74                 return S_LCDDIM;
75         default:
76                 return S_END;
77         }
78 }
79
80 static void refresh_app_cond(void)
81 {
82         trans_condition = 0;
83
84         if (check_lock_state(S_NORMAL))
85                 trans_condition |= MASK_NORMAL;
86         if (check_lock_state(S_LCDDIM))
87                 trans_condition |= MASK_DIM;
88         if (check_lock_state(S_LCDOFF))
89                 trans_condition |= MASK_OFF;
90 }
91
92 static void broadcast_pmlock_state_changed(enum state_t state)
93 {
94         int num_of_pmlock;
95
96         switch(state) {
97         case S_LCDON:
98         case S_LCDDIM:
99         case S_LCDOFF:
100                 break;
101         default:
102                 return;
103         }
104
105         num_of_pmlock = g_list_length(cond_head[state]);
106         if (num_of_pmlock > 1)
107                 return;
108
109         gdbus_signal_emit(NULL,
110                 DEVICED_PATH_DISPLAY,
111                 DEVICED_INTERFACE_DISPLAY,
112                 DEVICED_SIGNAL_POWER_LOCK_STATE_CHANGED,
113                 g_variant_new("(ii)", state, num_of_pmlock));
114 }
115
116 static void broadcast_pmlock_expired(pid_t pid, enum state_t state, const char* appid, time_t locktime)
117 {
118         time_t now;
119         double diff;
120         int ret;
121         const char* state_name = NULL;
122
123         time(&now);
124         diff = difftime(now, locktime);
125
126         display_plugin_state_get_name(state, &state_name);
127         CRITICAL_LOG("%s(%d) has held %s lock for a long time(%.0f s).",
128                         appid ? appid : "NULL", pid, state_name + 2, diff);
129
130         ret = gdbus_signal_emit(NULL,
131                                                 DEVICED_PATH_DISPLAY,
132                                                 DEVICED_INTERFACE_DISPLAY,
133                                                 "pmlock_expired",
134                                                 g_variant_new("(ii)", pid, (int)diff));
135         if (ret < 0)
136                 _E("Failed to send dbus pmlock_expired");
137 }
138
139 static void default_pmlock_check_cb(GVariant *var, void *user_data, GError *err)
140 {
141         pid_t pid = 0;
142         int detected = 0;
143         char *app_id = NULL;
144         enum state_t state = (enum state_t) user_data;
145         PmLockNode *node;
146
147         if (!var)
148                 return;
149
150         if (!g_variant_get_safe(var, "(iis)", &pid, &detected, &app_id)) {
151                 _E("Failed to get params from gvariant. expected:%s, type:%s", "(iis)", g_variant_get_type_string(var));
152                 goto out;
153         }
154
155         if (!detected)
156                 goto out;
157
158         node = find_node(state, pid);
159         if (!node)
160                 goto out;
161
162         broadcast_pmlock_expired(pid, state, app_id, node->time);
163
164 out:
165         if (app_id)
166                 g_free(app_id);
167         g_variant_unref(var);
168 }
169
170 static gboolean default_pmlock_check(void *data)
171 {
172         char *str;
173         PmLockNode *node;
174         enum state_t state;
175         pid_t pid;
176         int ret;
177
178         if (!data) {
179                 _E("Invalid parameter.");
180                 return G_SOURCE_REMOVE;
181         }
182
183         node = (PmLockNode *) data;
184         state = node->state;
185         pid = node->pid;
186
187         if (state == S_LCDOFF && display_panel_get_dpms_cached_state() == DPMS_ON) {
188                 _D("Lcd state is PM_LCD_POWER_ON");
189                 return G_SOURCE_CONTINUE;
190         }
191
192         /* Stop checking lock if process had been terminated */
193         if (kill(pid, 0) == -1) {
194                 node = find_node(state, pid);
195                 del_node(state, node);
196                 _I("Process %d not found. Stop checking lock.", pid);
197                 return G_SOURCE_REMOVE;
198         }
199
200         if (!is_app(pid)) {
201                 /* For (non-killable) daemon,
202                  * no need to ask resourced if it is abnormal lock */
203                 broadcast_pmlock_expired(pid, state, NULL, node->time);
204                 return G_SOURCE_CONTINUE;
205         }
206
207         switch (state) {
208         case S_NORMAL:
209                 str = "normal";
210                 break;
211         case S_LCDDIM:
212                 str = "lcddim";
213                 break;
214         case S_LCDOFF:
215                 str = "lcdoff";
216                 break;
217         default:
218                 _E("Invalid state.");
219                 return G_SOURCE_REMOVE;
220         }
221
222         ret = gdbus_call_async_with_reply(RESOURCED_BUS_NAME,
223                 RESOURCED_PATH_PROCESS,
224                 RESOURCED_INTERFACE_PROCESS,
225                 METHOD_APP_STATUS,
226                 g_variant_new("(is)", pid, str),
227                 default_pmlock_check_cb, -1, (void *)(intptr_t)state);
228         if (ret < 0)
229                 _E("Failed to call dbus method");
230
231         return G_SOURCE_CONTINUE;
232 }
233
234
235 PmLockNode *find_node(enum state_t s_index, pid_t pid)
236 {
237         GList *elem;
238         PmLockNode *t = NULL;
239
240         SYS_G_LIST_FOREACH(cond_head[s_index], elem, t) {
241                 if (t->pid == pid)
242                         return t;
243         }
244
245         return NULL;
246 }
247
248 PmLockNode *add_node(enum state_t s_index, pid_t pid, guint timeout_id,
249                 bool holdkey_block)
250 {
251         PmLockNode *n;
252         time_t now;
253
254         assert(g_display_plugin.config);
255
256         n = (PmLockNode *) calloc(1, sizeof(PmLockNode));
257         if (n == NULL) {
258                 _E("Not enough memory, failed to alloc lock node.");
259                 return NULL;
260         }
261
262         time(&now);
263         n->state = s_index;
264         n->pid = pid;
265         n->timeout_id = timeout_id;
266         n->time = now;
267         n->holdkey_block = holdkey_block;
268         /* LCDOFF lock should be maintained regardless of fg/bg state */
269         if (n->state == S_NORMAL || n->state == S_LCDDIM) {
270                 n->app_background = is_app_background(n->pid);
271                 if (n->app_background)
272                         _W("App(%d) requested %d lock in background.", n->pid, n->state);
273         }
274         n->broadcast_warning = true;
275
276         if (pid < INTERNAL_LOCK_BASE) {
277                 /* check if this lock node needs custom-defined lock checker.
278                  * n->warning_id would be 0 if fails to register the checker,
279                  * or there is no need to use that checker */
280                 if (g_display_plugin.config->pmlock_check)
281                         n->warning_id = g_display_plugin.config->pmlock_check(n);
282
283                 /* use default lock checker */
284                 if (!n->warning_id)
285                         n->warning_id = g_timeout_add_seconds(g_display_plugin.config->lockcheck_timeout,
286                                 default_pmlock_check, (gpointer)n);
287         }
288
289         SYS_G_LIST_APPEND(cond_head[s_index], n);
290
291         refresh_app_cond();
292
293         broadcast_pmlock_state_changed(s_index);
294
295         return n;
296 }
297
298 int del_node(enum state_t s_index, PmLockNode *n)
299 {
300         if (n == NULL)
301                 return 0;
302
303         SYS_G_LIST_REMOVE(cond_head[s_index], n);
304
305         /* delete timer */
306         if (n->timeout_id) {
307                 g_source_remove(n->timeout_id);
308                 n->timeout_id = 0;
309         }
310         if (n->warning_id) {
311                 g_source_remove(n->warning_id);
312                 n->warning_id = 0;
313         }
314         if (n->warning_param) {
315                 g_variant_unref(n->warning_param);
316                 n->warning_param = NULL;
317         }
318
319         broadcast_pmlock_state_changed(s_index);
320
321         free(n);
322         refresh_app_cond();
323         return 0;
324 }
325
326 int check_lock_condition(enum state_t state)
327 {
328         GList *elem;
329         PmLockNode *t = NULL;
330         int ret = false;
331         pid_t owner = getpid();
332         const char *state_name = NULL;
333
334         display_plugin_state_get_name(state, &state_name);
335         _D("check holdkey block : state of %s", state_name);
336
337         SYS_G_LIST_FOREACH(cond_head[state], elem, t) {
338                 if (t->pid != owner && t->app_background == false) {
339                         ret = true;
340                         _I("state change was blocked by pid(%d)!", t->pid);
341                         break;
342                 }
343         }
344
345         return ret;
346 }
347
348 int check_holdkey_block(enum state_t state)
349 {
350         GList *elem;
351         PmLockNode *t = NULL;
352         int ret = 0;
353         const char *state_name = NULL;
354
355         display_plugin_state_get_name(state, &state_name);
356         _I("Check holdkey block: state of %s", state_name);
357
358         if (custom_holdkey_block == true) {
359                 _I("Custom hold key blocked");
360                 return 1;
361         }
362
363         SYS_G_LIST_FOREACH(cond_head[state], elem, t) {
364                 if (t->holdkey_block == true) {
365                         ret = 1;
366                         _I("Hold key blocked by pid(%d).", t->pid);
367                         break;
368                 }
369         }
370
371         return ret;
372 }
373
374 int delete_condition(enum state_t state)
375 {
376         GList *elem, *next;
377         PmLockNode *t = NULL;
378         const char *state_name = NULL;
379
380         display_plugin_state_get_name(state, &state_name);
381         _I("delete condition : state of %s", state_name);
382
383         if (!cond_head[state])
384                 return 0;
385
386         SYS_G_LIST_FOREACH_SAFE(cond_head[state], elem, next, t) {
387                 if (t->timeout_id > 0) {
388                         g_source_remove(t->timeout_id);
389                         t->timeout_id = 0;
390                 }
391                 if (state == S_LCDOFF)
392                         set_process_active(false, t->pid);
393                 _I("delete node of pid(%d)", t->pid);
394                 set_unlock_time(t->pid, state);
395                 del_node(state, t);
396         }
397
398         SYS_G_LIST_FREE_LIST(cond_head[state]);
399         cond_head[state] = NULL;
400
401         broadcast_pmlock_state_changed(state);
402
403         return 0;
404 }
405
406 void print_node(int next)
407 {
408         int ret;
409         GList *elem;
410         PmLockNode *n;
411         char buf[30];
412         time_t now;
413         double diff;
414
415         if (next <= S_START || next >= S_END)
416                 return;
417
418         time(&now);
419         SYS_G_LIST_FOREACH(cond_head[next], elem, n) {
420                 diff = difftime(now, n->time);
421                 ctime_r(&n->time, buf);
422                 buf[strlen(buf) - 1] = 0;
423
424                 if (diff > LOCK_TIME_WARNING) {
425                         if (diff > LOCK_TIME_WARNING * 60 && n->pid < INTERNAL_LOCK_BASE && n->broadcast_warning) {
426                                 ret = gdbus_signal_emit(NULL,
427                                                                                 DEVICED_PATH_DISPLAY,
428                                                                                 DEVICED_INTERFACE_DISPLAY,
429                                                                                 "pmlock_over",
430                                                                                 g_variant_new("(i)", n->pid));
431                                 if (ret < 0)
432                                         _E("Failed to send dbus signal pmlock_over.");
433                                 n->broadcast_warning = false;
434                         }
435                         _W("Over(%.0f s) pid( %5d) lock time(%s)", diff, n->pid, buf);
436                 } else
437                         _I("Pid(%5d) lock time(%s)", n->pid, buf);
438         }
439 }
440
441 void makeup_trans_condition(void)
442 {
443         check_processes(S_NORMAL);
444         check_processes(S_LCDDIM);
445         check_processes(S_LCDOFF);
446         refresh_app_cond();
447 }
448
449 /* If a lock node in the given state had been dead, delete it form lock list,
450  * and returns 1, else returns 0 */
451 int check_processes(enum state_t prohibit_state)
452 {
453         GList *elem, *next;
454         PmLockNode *t = NULL;
455         int ret = 0;
456
457         SYS_G_LIST_FOREACH_SAFE(cond_head[prohibit_state], elem, next, t) {
458                 if (t->pid == -1 || (t->pid < INTERNAL_LOCK_BASE && kill(t->pid, 0) == -1)) {
459                         _E("%d process does not exist, delete the REQ"
460                                 " - prohibit state %d ",
461                                 t->pid, prohibit_state);
462                         ret = 1;
463                         set_unlock_time(t->pid, prohibit_state);
464                         del_node(prohibit_state, t);
465                 }
466         }
467
468         return ret;
469 }
470
471 int get_trans_condition(void)
472 {
473         return trans_condition;
474 }
475
476 GList *get_cond_head(enum state_t s_index)
477 {
478         return cond_head[s_index];
479 }
480
481 int display_app_background(void *data)
482 {
483         pid_t pid;
484         PmLockNode *node;
485
486         pid = *(pid_t *)data;
487
488         node = find_node(S_NORMAL, pid);
489         if (node) {
490                 node->app_background = true;
491                 _I("App(%d) goes background. LCD_NORMAL will be unlocked.", pid);
492         }
493
494         node = find_node(S_LCDDIM, pid);
495         if (node) {
496                 node->app_background = true;
497                 _I("App(%d) goes background. LCD_DIM will be unlocked.", pid);
498         }
499
500         return 0;
501 }
502
503 int display_app_foreground(void *data)
504 {
505         pid_t pid;
506         PmLockNode *node;
507
508         pid = *(pid_t *)data;
509
510         node = find_node(S_NORMAL, pid);
511         if (node) {
512                 node->app_background = false;
513                 _I("App(%d) goes foreground. LCD_NORMAL will be locked.", pid);
514         }
515
516         node = find_node(S_LCDDIM, pid);
517         if (node) {
518                 node->app_background = false;
519                 _I("App(%d) goes foreground. LCD_DIM will be locked.", pid);
520         }
521
522         return 0;
523 }
524
525 int display_app_terminated(void *data)
526 {
527         pid_t pid;
528         PmLockNode *node;
529         enum state_t state;
530         enum state_t cur = get_pm_cur_state();
531         bool current_unlocked = false;
532
533         pid = *(pid_t *)data;
534
535         /* release lock if it is holding locks */
536         for (state = S_START; state < S_END; ++state) {
537                 node = find_node(state, pid);
538                 if (node) {
539                         _W("App=%d is terminated without releasing lockstate=%d. deviced unlocks it.", pid, state);
540                         del_node(state, node);
541                         set_unlock_time(pid, state);
542                         if (state == cur)
543                                 current_unlocked = true;
544                 }
545         }
546
547         /* Change state only when all three conditions below are satisfied.
548          *   1. the lock released due to the termination of the application
549          *      must be a lock for the current state
550          *   2. after releasing the lock, there is no foreground lock
551          *      for the current state
552          *   3. there should be no running state-transition timer
553          *
554          * This emulates already expired transition timer */
555         if (current_unlocked && no_foreground_lock(cur) && no_transition_timer())
556                 reset_timeout(0);
557
558         return 0;
559 }
560
561 /* update transition condition for application requrements */
562 void update_lock_timer(PMMsg *data,
563                 PmLockNode *node, guint timeout_id)
564 {
565         time_t now;
566
567         if (data->timeout > 0) {
568                 time(&now);
569                 node->time = now;
570         }
571
572         if (node->timeout_id)
573                 g_source_remove(node->timeout_id);
574         node->timeout_id = timeout_id;
575 }
576
577 int display_lock_set_custom_holdkey_block(bool blocked)
578 {
579         custom_holdkey_block = blocked;
580         return 0;
581 }
582
583 int display_lock_get_custom_holdkey_block(bool *blocked)
584 {
585         if (!blocked)
586                 return -EINVAL;
587
588         *blocked = custom_holdkey_block;
589         return 0;
590 }
591