battery-minus

Pebble activity tracker that records battery events
git clone https://git.instinctive.eu/battery-minus.git
Log | Files | Refs | README | LICENSE

battery-minus.c (18997B)


      1 /*
      2  * Copyright (c) 2015-2016, Natacha Porté
      3  *
      4  * Permission to use, copy, modify, and distribute this software for any
      5  * purpose with or without fee is hereby granted, provided that the above
      6  * copyright notice and this permission notice appear in all copies.
      7  *
      8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
      9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     15  */
     16 
     17 #include <inttypes.h>
     18 #include <pebble.h>
     19 #include "dict_tools.h"
     20 #include "simple_dialog.h"
     21 #include "storage.h"
     22 
     23 #undef DISPLAY_TEST_DATA
     24 
     25 static Window *window;
     26 static SimpleMenuLayer *menu_layer;
     27 static SimpleMenuSection menu_section;
     28 static SimpleMenuItem menu_items[PAGE_LENGTH + 2];
     29 
     30 static struct event current_page[PAGE_LENGTH];
     31 static const char *titles[PAGE_LENGTH];
     32 static const char *dates[PAGE_LENGTH];
     33 static int cfg_wakeup_time = -1;
     34 static char send_status[64];
     35 
     36 static void
     37 do_start_worker(int index, void *context);
     38 
     39 static void
     40 do_stop_worker(int index, void *context);
     41 
     42 /*************
     43  * UTILITIES *
     44  *************/
     45 
     46 static void
     47 close_app(void) {
     48 	window_stack_pop_all(true);
     49 }
     50 
     51 static unsigned
     52 first_index(struct event *page, size_t page_length) {
     53 	unsigned j;
     54 
     55 	for (j = 1;
     56 	    j < page_length && page[j - 1].time < page[j].time;
     57 	    j += 1);
     58 	if (j >= page_length || !page[j].time) j = 0;
     59 
     60 	return j;
     61 }
     62 
     63 static void
     64 mark_menu_dirty(void) {
     65 	if (!menu_layer) return;
     66 	layer_mark_dirty(simple_menu_layer_get_layer(menu_layer));
     67 }
     68 
     69 /**********************
     70  * DATA UPLOAD TO WEB *
     71  **********************/
     72 
     73 #define MSG_KEY_LAST_SENT	110
     74 #define MSG_KEY_LAST_POSTED	120
     75 #define MSG_KEY_DATA_TIME	210
     76 #define MSG_KEY_DATA_LINE	220
     77 #define MSG_KEY_CFG_WAKEUP_TIME	320
     78 
     79 static unsigned sent_index;
     80 static unsigned sent_done;
     81 static time_t sent_last_key;
     82 
     83 static const char keyword_anomalous[] = "error";
     84 static const char keyword_charge_start[] = "charge";
     85 static const char keyword_charge_stop[] = "dischg";
     86 static const char keyword_charging[] = "+";
     87 static const char keyword_discharging[] = "-";
     88 static const char keyword_unknown[] = "unknown";
     89 static const char keyword_start[] = "start";
     90 static const char keyword_start_charging[] = "start+";
     91 static const char keyword_stop[] = "stop";
     92 static const char keyword_stop_charging[] = "stop+";
     93 
     94 static bool
     95 event_csv_image(char *buffer, size_t size, struct event *event) {
     96 	struct tm *tm;
     97 	size_t ret;
     98 	int i;
     99 	const char *keyword;
    100 	uint8_t int_1, int_2;
    101 	bool has_int_2;
    102 
    103 	if (!buffer || !event) return false;
    104 
    105 	tm = gmtime(&event->time);
    106 	if (!tm) {
    107 		APP_LOG(APP_LOG_LEVEL_ERROR, "event_csv_image: "
    108 		    "Unable to get UTC time for %" PRIi32, event->time);
    109 		return false;
    110 	}
    111 
    112 	ret = strftime(buffer, size, "%FT%TZ", tm);
    113 	if (!ret) {
    114 		APP_LOG(APP_LOG_LEVEL_ERROR, "event_csv_image: "
    115 		    "Unable to build RFC-3339 representation of %" PRIi32,
    116 		    event->time);
    117 		return false;
    118 	}
    119 
    120 	if (ret >= size) {
    121 		APP_LOG(APP_LOG_LEVEL_ERROR, "event_csv_image: "
    122 		    "Unexpected returned value %zu of strftime on buffer %zu",
    123 		    ret, size);
    124 		return false;
    125 	}
    126 
    127 	switch (event->before) {
    128 	    case UNKNOWN:
    129 		keyword = keyword_unknown;
    130 		int_1 = event->after;
    131 		has_int_2 = false;
    132 		break;
    133 
    134 	    case APP_STARTED:
    135 		keyword = (event->after & 0x80)
    136 		    ? keyword_start_charging : keyword_start;
    137 		int_1 = event->after & 0x7f;
    138 		has_int_2 = false;
    139 		break;
    140 
    141 	    case APP_CLOSED:
    142 		keyword = (event->after & 0x80)
    143 		    ? keyword_stop_charging : keyword_stop;
    144 		int_1 = event->after & 0x7f;
    145 		has_int_2 = false;
    146 		break;
    147 
    148 	    case ANOMALOUS_VALUE:
    149 		keyword = keyword_anomalous;
    150 		int_1 = event->after;
    151 		has_int_2 = false;
    152 		break;
    153 
    154 	    default:
    155 		keyword = (event->before & 0x80)
    156 		    ? ((event->after & 0x80)
    157 		      ? keyword_charging : keyword_charge_stop)
    158 		    : ((event->after & 0x80)
    159 		      ? keyword_charge_start : keyword_discharging);
    160 		int_1 = event->after & 0x7f;
    161 		int_2 = event->before & 0x7f;
    162 		has_int_2 = true;
    163 		break;
    164 	}
    165 
    166 	if (has_int_2)
    167 		i = snprintf(buffer + ret, size - ret,
    168 		    ",%s,%" PRIu8 ",%" PRIu8, keyword, int_1, int_2);
    169 	else
    170 		i = snprintf(buffer + ret, size - ret,
    171 		    ",%s,%" PRIu8, keyword, int_1);
    172 
    173 	if (i <= 0) {
    174 		APP_LOG(APP_LOG_LEVEL_ERROR, "event_csv_image: "
    175 		    "Unexpected return value %d from snprintf", i);
    176 		return false;
    177 	}
    178 
    179 	return true;
    180 }
    181 
    182 static bool
    183 send_event(struct event *event) {
    184 	AppMessageResult msg_result;
    185 	DictionaryIterator *iter;
    186 	DictionaryResult dict_result;
    187 	char buffer[64];
    188 	bool result = true;
    189 
    190 	if (!event) return false;
    191 
    192 	if (!event_csv_image(buffer, sizeof buffer, event))
    193 		return false;
    194 
    195 	msg_result = app_message_outbox_begin(&iter);
    196 	if (msg_result) {
    197 		APP_LOG(APP_LOG_LEVEL_ERROR,
    198 		    "send_event: app_message_outbox_begin returned %d",
    199 		    (int)msg_result);
    200 		return false;
    201 	}
    202 
    203 	dict_result = dict_write_int(iter, MSG_KEY_DATA_TIME,
    204 	    &event->time, sizeof event->time, true);
    205 	if (dict_result != DICT_OK) {
    206 		APP_LOG(APP_LOG_LEVEL_ERROR,
    207 		    "send_event: [%d] unable to add data time %" PRIi32,
    208 		    (int)dict_result, event->time);
    209 		result = false;
    210 	}
    211 
    212 	dict_result = dict_write_cstring(iter, MSG_KEY_DATA_LINE, buffer);
    213 	if (dict_result != DICT_OK) {
    214 		APP_LOG(APP_LOG_LEVEL_ERROR,
    215 		    "send_event: [%d] unable to add data line \"%s\"",
    216 		    (int)dict_result, buffer);
    217 		result = false;
    218 	}
    219 
    220 	msg_result = app_message_outbox_send();
    221 	if (msg_result) {
    222 		APP_LOG(APP_LOG_LEVEL_ERROR,
    223 		    "send_event: app_mesage_outbox_send returned %d",
    224 		    (int)msg_result);
    225 		result = false;
    226 	}
    227 
    228 	return result;
    229 }
    230 
    231 static void
    232 handle_nothing_to_do(void) {
    233 	if (launch_reason() == APP_LAUNCH_WAKEUP)
    234 		close_app();
    235 	else {
    236 		snprintf(send_status, sizeof send_status, "Done (0)");
    237 		mark_menu_dirty();
    238 	}
    239 }
    240 
    241 static void
    242 handle_last_sent(Tuple *tuple) {
    243 	time_t t = tuple_int(tuple);
    244 
    245 	sent_index = first_index(current_page, PAGE_LENGTH);
    246 
    247 	if (!current_page[sent_index].time) {
    248 		/* empty page */
    249 		handle_nothing_to_do();
    250 		return;
    251 	}
    252 
    253 	if (t)
    254 		while (current_page[sent_index].time <= t) {
    255 			unsigned next_index = (sent_index + 1) % PAGE_LENGTH;
    256 			if (current_page[sent_index].time
    257 			     > current_page[next_index].time) {
    258 				/* end of page reached without match */
    259 				handle_nothing_to_do();
    260 				return;
    261 			}
    262 			sent_index = next_index;
    263 		}
    264 
    265 	snprintf(send_status, sizeof send_status, "0 sent");
    266 	mark_menu_dirty();
    267 
    268 	send_event(current_page + sent_index);
    269 }
    270 
    271 static void
    272 inbox_received_handler(DictionaryIterator *iterator, void *context) {
    273 	Tuple *tuple;
    274 	(void)context;
    275 
    276 	for (tuple = dict_read_first(iterator);
    277 	    tuple;
    278 	    tuple = dict_read_next(iterator)) {
    279 		switch (tuple->key) {
    280 		    case MSG_KEY_LAST_SENT:
    281 			handle_last_sent(tuple);
    282 			break;
    283 
    284 		    case MSG_KEY_LAST_POSTED:
    285 			if (tuple_int(tuple) == sent_last_key
    286 			    && launch_reason() == APP_LAUNCH_WAKEUP) {
    287 				close_app();
    288 			}
    289 			break;
    290 
    291 		    case MSG_KEY_CFG_WAKEUP_TIME:
    292 			cfg_wakeup_time = tuple_int(tuple);
    293 			persist_write_int(MSG_KEY_CFG_WAKEUP_TIME,
    294 			    cfg_wakeup_time + 1);
    295 			break;
    296 
    297 		    default:
    298 			APP_LOG(APP_LOG_LEVEL_ERROR,
    299 			    "Unknown key %" PRIu32 " in received message",
    300 			    tuple->key);
    301 			break;
    302 		}
    303 	}
    304 }
    305 
    306 static void
    307 outbox_sent_handler(DictionaryIterator *iterator, void *context) {
    308 	unsigned next_index = (sent_index + 1) % PAGE_LENGTH;
    309 	(void)iterator;
    310 	(void)context;
    311 
    312 	sent_done += 1;
    313 
    314 	if (current_page[sent_index].time <= current_page[next_index].time) {
    315 		sent_index = next_index;
    316 		send_event(current_page + next_index);
    317 		snprintf(send_status, sizeof send_status, "%u sent",
    318 		    sent_done);
    319 	} else {
    320 		sent_last_key = current_page[sent_index].time;
    321 		snprintf(send_status, sizeof send_status, "Done (%u)",
    322 		    sent_done);
    323 		mark_menu_dirty();
    324 	}
    325 }
    326 
    327 static void
    328 outbox_failed_handler(DictionaryIterator *iterator, AppMessageResult reason,
    329     void *context) {
    330 	(void)iterator;
    331 	(void)context;
    332 	APP_LOG(APP_LOG_LEVEL_ERROR, "Outbox failed: 0x%x", (unsigned)reason);
    333 	if (launch_reason() == APP_LAUNCH_WAKEUP)
    334 		close_app();
    335 	snprintf(send_status, sizeof send_status, "Outbox failed 0x%x",
    336 	    (unsigned)reason);
    337 }
    338 
    339 /*************
    340  * MENU ITEM *
    341  *************/
    342 
    343 #define SET_BUF(dest, src) (strcpy(dest, src ""), (int)(sizeof src) - 1)
    344 
    345 static const char *
    346 strdup(char *buffer) {
    347 	size_t length = strlen(buffer);
    348 	char *result = malloc(length + 1);
    349 	if (result) memcpy(result, buffer, length + 1);
    350 	return result;
    351 }
    352 
    353 static void
    354 init_strings(void) {
    355 	char buffer[256];
    356 	int ret;
    357 	struct tm *tm;
    358 	unsigned j = first_index(current_page, PAGE_LENGTH);
    359 
    360 	for (unsigned i = 0; i < PAGE_LENGTH; i += 1) {
    361 		if (!current_page[j].time) {
    362 			titles[i] = dates[i] = 0;
    363 			continue;
    364 		}
    365 
    366 		tm = localtime(&current_page[j].time);
    367 		ret = strftime(buffer, sizeof buffer, "%Y-%m-%d %H:%M:%S", tm);
    368 		dates[i] = ret ? strdup(buffer) : 0;
    369 
    370 		switch (current_page[j].before) {
    371 		    case UNKNOWN:
    372 			snprintf(buffer, sizeof buffer,
    373 			    "%u%%%c",
    374 			    (unsigned)(current_page[j].after & 0x7f),
    375 			    (current_page[j].after & 0x80) ? '+' : '-');
    376 			break;
    377 
    378 		    case APP_STARTED:
    379 			snprintf(buffer, sizeof buffer,
    380 			    "Start %u%%%c",
    381 			    (unsigned)(current_page[j].after & 0x7f),
    382 			    (current_page[j].after & 0x80) ? '+' : '-');
    383 			break;
    384 
    385 		    case APP_CLOSED:
    386 			snprintf(buffer, sizeof buffer,
    387 			    "Close %u%%%c",
    388 			    (unsigned)(current_page[j].after & 0x7f),
    389 			    (current_page[j].after & 0x80) ? '+' : '-');
    390 			break;
    391 
    392 		    case ANOMALOUS_VALUE:
    393 			snprintf(buffer, sizeof buffer,
    394 			    "Anomalous %u",
    395 			    (unsigned)(current_page[j].after));
    396 			break;
    397 
    398 		    default:
    399 			if ((current_page[j].before & 0x80)
    400 			    == (current_page[j].after & 0x80)) {
    401 				snprintf(buffer, sizeof buffer,
    402 				    "%u%% %c> %u%%",
    403 				    (unsigned)(current_page[j].before & 0x7f),
    404 				    (current_page[j].after & 0x80) ? '+' : '-',
    405 				    (unsigned)(current_page[j].after & 0x7f));
    406 				break;
    407 			}
    408 
    409 			if ((current_page[j].before & 0x7f)
    410 			    == (current_page[j].after & 0x7f))
    411 				snprintf (buffer, sizeof buffer,
    412 				    "%s %u%%",
    413 				    (current_page[j].after & 0x80)
    414 				    ? "Charge" : "Discharge",
    415 				    (unsigned)(current_page[j].after & 0x7f));
    416 			else
    417 				snprintf (buffer, sizeof buffer,
    418 				    "%s %u%% -> %u%%",
    419 				    (current_page[j].after & 0x80)
    420 				    ? "Chg" : "Disch",
    421 				    (unsigned)(current_page[j].before & 0x7f),
    422 				    (unsigned)(current_page[j].after & 0x7f));
    423 			break;
    424 		}
    425 
    426 		titles[i] = strdup(buffer);
    427 
    428 		j = (j + 1) % PAGE_LENGTH;
    429 	}
    430 }
    431 
    432 static time_t
    433 latest_event(void) {
    434 	time_t result = current_page[0].time;
    435 	for (unsigned j = 1; j < PAGE_LENGTH; j += 1) {
    436 		if (result < current_page[j].time) {
    437 			result = current_page[j].time;
    438 		}
    439 	}
    440 	return result;
    441 }
    442 
    443 static void
    444 rebuild_menu(void) {
    445 	unsigned i = PAGE_LENGTH;
    446 	bool is_empty = true;
    447 	time_t old_latest = latest_event();
    448 
    449 	persist_read_data(1, current_page, sizeof current_page);
    450 
    451 	if (old_latest != latest_event()) {
    452 		for (unsigned i = 0; i < PAGE_LENGTH; i += 1) {
    453 			free((void *)dates[i]);
    454 			free((void *)titles[i]);
    455 		}
    456 		init_strings();
    457 	}
    458 
    459 	menu_section.title = 0;
    460 	menu_section.items = menu_items;
    461 	menu_section.num_items = 0;
    462 
    463 	menu_items[menu_section.num_items] = (SimpleMenuItem){
    464 	    .title = send_status,
    465 	    .subtitle = 0,
    466 	    .callback = 0,
    467 	    .icon = 0
    468 	};
    469 	menu_section.num_items += 1;
    470 
    471 	if (app_worker_is_running()) {
    472 		menu_items[menu_section.num_items] = (SimpleMenuItem){
    473 		    .title = "Stop worker",
    474 		    .callback = &do_stop_worker
    475 		};
    476 		menu_section.num_items += 1;
    477 	} else {
    478 		menu_items[menu_section.num_items] = (SimpleMenuItem){
    479 		    .title = "Start worker",
    480 		    .callback = &do_start_worker
    481 		};
    482 		menu_section.num_items += 1;
    483 	}
    484 
    485 	while (i) {
    486 		i -= 1;
    487 		if (!titles[i]) continue;
    488 		menu_items[menu_section.num_items].title = titles[i];
    489 		menu_items[menu_section.num_items].subtitle = dates[i];
    490 		menu_items[menu_section.num_items].icon = 0;
    491 		menu_items[menu_section.num_items].callback = 0;
    492 		menu_section.num_items += 1;
    493 		is_empty = false;
    494 	}
    495 
    496 	if (is_empty) {
    497 		menu_items[menu_section.num_items].title = "No event recorded";
    498 		menu_items[menu_section.num_items].subtitle = 0;
    499 		menu_items[menu_section.num_items].icon = 0;
    500 		menu_items[menu_section.num_items].callback = 0;
    501 		menu_section.num_items += 1;
    502 	}
    503 }
    504 
    505 /****************
    506  * MENU ACTIONS *
    507  ****************/
    508 
    509 static void
    510 do_start_worker(int index, void *context) {
    511 	(void)index;
    512 	(void)context;
    513 	char buffer[256];
    514 	AppWorkerResult result = app_worker_launch();
    515 
    516 	switch (result) {
    517 	    case APP_WORKER_RESULT_SUCCESS:
    518 		push_simple_dialog("Worker start requested.", true);
    519 		break;
    520 	    case APP_WORKER_RESULT_ALREADY_RUNNING:
    521 		push_simple_dialog("Worker is already running.", true);
    522 		break;
    523 	    case APP_WORKER_RESULT_ASKING_CONFIRMATION:
    524 		APP_LOG(APP_LOG_LEVEL_INFO,
    525 		    "Frirmware requesting confirmation, skipping UI");
    526 		break;
    527 	    default:
    528 		snprintf(buffer, sizeof buffer,
    529 		   "Unexpected result %d",
    530 		   (int)result);
    531 		push_simple_dialog(buffer, false);
    532 	}
    533 }
    534 
    535 static void
    536 do_stop_worker(int index, void *context) {
    537 	(void)index;
    538 	(void)context;
    539 	char buffer[256];
    540 	AppWorkerResult result = app_worker_kill();
    541 
    542 	switch (result) {
    543 	    case APP_WORKER_RESULT_SUCCESS:
    544 		push_simple_dialog("Worker stop requested.", true);
    545 		break;
    546 	    case APP_WORKER_RESULT_NOT_RUNNING:
    547 		push_simple_dialog("Worker is already stopped.", true);
    548 		break;
    549 	    case APP_WORKER_RESULT_DIFFERENT_APP:
    550 		push_simple_dialog("A different worker is running.", true);
    551 		break;
    552 	    case APP_WORKER_RESULT_ASKING_CONFIRMATION:
    553 		APP_LOG(APP_LOG_LEVEL_INFO,
    554 		    "Frirmware requesting confirmation, skipping UI");
    555 		break;
    556 	    default:
    557 		snprintf(buffer, sizeof buffer,
    558 		   "Unexpected result %d",
    559 		   (int)result);
    560 		push_simple_dialog(buffer, false);
    561 	}
    562 }
    563 
    564 /*********************
    565  * WINDOW MANAGEMENT *
    566  *********************/
    567 
    568 static void
    569 window_appear(Window *window) {
    570 	rebuild_menu();
    571 }
    572 
    573 static void
    574 window_load(Window *window) {
    575 	Layer *window_layer = window_get_root_layer(window);
    576 	GRect bounds = layer_get_bounds(window_layer);
    577 
    578 	rebuild_menu();
    579 
    580 	menu_layer = simple_menu_layer_create(bounds, window,
    581 	    &menu_section, 1, 0);
    582 	simple_menu_layer_set_selected_index(menu_layer, 1, false);
    583 
    584 	layer_add_child(window_layer, simple_menu_layer_get_layer(menu_layer));
    585 }
    586 
    587 static void
    588 window_unload(Window *window) {
    589 	simple_menu_layer_destroy(menu_layer);
    590 }
    591 
    592 /**********************************
    593  * INTIALIZATION AND FINALIZATION *
    594  **********************************/
    595 
    596 static void
    597 init(void) {
    598 	snprintf(send_status, sizeof send_status, "Waiting for JS");
    599 
    600 	cfg_wakeup_time = persist_read_int(MSG_KEY_CFG_WAKEUP_TIME) - 1;
    601 	wakeup_cancel_all();
    602 
    603 	persist_read_data(1, current_page, sizeof current_page);
    604 
    605 #ifdef DISPLAY_TEST_DATA
    606 	current_page[0].time = 1449738000; /* 2015-12-10T10:00:00 */
    607 	current_page[0].before = APP_STARTED;
    608 	current_page[0].after = 90;
    609 	current_page[1].time = 1449741600; /* 2015-12-10T11:00:00 */
    610 	current_page[1].before = 90;
    611 	current_page[1].after = 80;
    612 	current_page[2].time = 1449742980; /* 2015-12-10T11:23:00 */
    613 	current_page[2].before = ANOMALOUS_VALUE;
    614 	current_page[2].after = 131;
    615 	current_page[3].time = 1449743160; /* 2015-12-10T11:26:00 */
    616 	current_page[3].before = UNKNOWN;
    617 	current_page[3].after = 70;
    618 	current_page[4].time = 1449743460; /* 2015-12-10T11:31:00 */
    619 	current_page[4].before = 70;
    620 	current_page[4].after = 128 | 70;
    621 	current_page[5].time = 1449743940; /* 2015-12-10T11:39:00 */
    622 	current_page[5].before = 128 | 70;
    623 	current_page[5].after = 128 | 80;
    624 	current_page[6].time = 1449744000; /* 2015-12-10T11:40:00 */
    625 	current_page[6].before = 128 | 80;
    626 	current_page[6].after = 80;
    627 	current_page[7].time = 1449744420; /* 2015-12-10T11:47:00 */
    628 	current_page[7].before = 80;
    629 	current_page[7].after = 128 | 70;
    630 	current_page[8].time = 1449744660; /* 2015-12-10T11:51:00 */
    631 	current_page[8].before = 128 | 70;
    632 	current_page[8].after = 80;
    633 	current_page[9].time = 1449744660; /* 2015-12-10T11:51:00 */
    634 	current_page[9].before = 80;
    635 	current_page[9].after = 90;
    636 	current_page[10].time = 1449745140; /* 2015-12-10T11:59:00 */
    637 	current_page[10].before = 90;
    638 	current_page[10].after = 128 | 100;
    639 	current_page[11].time = 1449745260; /* 2015-12-10T12:01:00 */
    640 	current_page[11].before = 128 | 100;
    641 	current_page[11].after = 128 | 80;
    642 	current_page[12].time = 1449745620; /* 2015-12-10T12:07:00 */
    643 	current_page[12].before = 128 | 80;
    644 	current_page[12].after = 60;
    645 	current_page[13].time = 1449745800; /* 2015-12-10T12:10:00 */
    646 	current_page[13].before = APP_CLOSED;
    647 	current_page[13].after = 60;
    648 	current_page[14].time = 1449846060; /* 2015-12-11T16:01:00 */
    649 	current_page[14].before = APP_STARTED;
    650 	current_page[14].after = 128 | 40;
    651 	current_page[15].time = 1449846480; /* 2015-12-11T16:08:00 */
    652 	current_page[15].before = 128 | 40;
    653 	current_page[15].after = 128 | 40;
    654 	current_page[16].time = 1449846660; /* 2015-12-11T16:11:00 */
    655 	current_page[16].before = UNKNOWN;
    656 	current_page[16].after = 128 | 60;
    657 	current_page[17].time = 1449846780; /* 2015-12-11T16:13:00 */
    658 	current_page[17].before = APP_CLOSED;
    659 	current_page[17].after = 128 | 60;
    660 	for (unsigned i = 18; i < PAGE_LENGTH; i += 1) {
    661 		current_page[i].time = 0;
    662 		current_page[i].before = 0;
    663 		current_page[i].after = 0;
    664 	}
    665 #else
    666 	if (launch_reason() == APP_LAUNCH_WAKEUP) {
    667 		push_simple_dialog("Battery- Auto Sync", true);
    668 		app_message_register_inbox_received(inbox_received_handler);
    669 		app_message_register_outbox_failed(outbox_failed_handler);
    670 		app_message_register_outbox_sent(outbox_sent_handler);
    671 		app_message_open(256, 2048);
    672 		return;
    673 	}
    674 #endif
    675 
    676 	init_strings();
    677 
    678 	window = window_create();
    679 	window_set_window_handlers(window, (WindowHandlers) {
    680 	    .load = window_load,
    681 	    .unload = window_unload,
    682 	    .appear = window_appear,
    683 	});
    684 	window_stack_push(window, true);
    685 
    686 	app_message_register_inbox_received(inbox_received_handler);
    687 	app_message_register_outbox_failed(outbox_failed_handler);
    688 	app_message_register_outbox_sent(outbox_sent_handler);
    689 	app_message_open(256, 2048);
    690 }
    691 
    692 static void
    693 deinit(void) {
    694 	window_destroy(window);
    695 
    696 	for (unsigned i = 0; i < PAGE_LENGTH; i += 1) {
    697 		if (titles[i]) free((void *)titles[i]);
    698 		titles[i] = 0;
    699 
    700 		if (dates[i]) free((void *)dates[i]);
    701 		dates[i] = 0;
    702 	}
    703 
    704 	if (cfg_wakeup_time >= 0) {
    705 		WakeupId res;
    706 		time_t now = time(0);
    707 		time_t t = clock_to_timestamp(TODAY,
    708 		    cfg_wakeup_time / 60, cfg_wakeup_time % 60);
    709 
    710 		if (t - now > 6 * 86400)
    711 			t -= 6 * 86400;
    712 		else if (t - now <= 120)
    713 			t += 86400;
    714 
    715 		res = wakeup_schedule(t, 0, true);
    716 
    717 		if (res < 0)
    718 			APP_LOG(APP_LOG_LEVEL_ERROR,
    719 			    "wakeup_schedule(%" PRIi32 ", 0, true)"
    720 			    " returned %" PRIi32,
    721 			    t, res);
    722 	}
    723 }
    724 
    725 int
    726 main(void) {
    727 	init();
    728 	app_event_loop();
    729 	deinit();
    730 }