1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <errno.h>
18 #include <fcntl.h>
19 #include <linux/input.h>
20 #include <pthread.h>
21 #include <stdarg.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <sys/stat.h>
26 #include <sys/time.h>
27 #include <sys/types.h>
28 #include <time.h>
29 #include <unistd.h>
30 
31 #include <cutils/android_reboot.h>
32 
33 #include "common.h"
34 #include "roots.h"
35 #include "device.h"
36 #include "minui/minui.h"
37 #include "screen_ui.h"
38 #include "ui.h"
39 
40 #define UI_WAIT_KEY_TIMEOUT_SEC    120
41 
42 // There's only (at most) one of these objects, and global callbacks
43 // (for pthread_create, and the input event system) need to find it,
44 // so use a global variable.
45 static RecoveryUI* self = NULL;
46 
RecoveryUI()47 RecoveryUI::RecoveryUI() :
48     key_queue_len(0),
49     key_last_down(-1),
50     key_long_press(false),
51     key_down_count(0),
52     enable_reboot(true),
53     consecutive_power_keys(0),
54     consecutive_alternate_keys(0),
55     last_key(-1) {
56     pthread_mutex_init(&key_queue_mutex, NULL);
57     pthread_cond_init(&key_queue_cond, NULL);
58     self = this;
59     memset(key_pressed, 0, sizeof(key_pressed));
60 }
61 
Init()62 void RecoveryUI::Init() {
63     ev_init(input_callback, NULL);
64     pthread_create(&input_t, NULL, input_thread, NULL);
65 }
66 
67 
input_callback(int fd,uint32_t epevents,void * data)68 int RecoveryUI::input_callback(int fd, uint32_t epevents, void* data)
69 {
70     struct input_event ev;
71     int ret;
72 
73     ret = ev_get_input(fd, epevents, &ev);
74     if (ret)
75         return -1;
76 
77     if (ev.type == EV_SYN) {
78         return 0;
79     } else if (ev.type == EV_REL) {
80         if (ev.code == REL_Y) {
81             // accumulate the up or down motion reported by
82             // the trackball.  When it exceeds a threshold
83             // (positive or negative), fake an up/down
84             // key event.
85             self->rel_sum += ev.value;
86             if (self->rel_sum > 3) {
87                 self->process_key(KEY_DOWN, 1);   // press down key
88                 self->process_key(KEY_DOWN, 0);   // and release it
89                 self->rel_sum = 0;
90             } else if (self->rel_sum < -3) {
91                 self->process_key(KEY_UP, 1);     // press up key
92                 self->process_key(KEY_UP, 0);     // and release it
93                 self->rel_sum = 0;
94             }
95         }
96     } else {
97         self->rel_sum = 0;
98     }
99 
100     if (ev.type == EV_KEY && ev.code <= KEY_MAX)
101         self->process_key(ev.code, ev.value);
102 
103     return 0;
104 }
105 
106 // Process a key-up or -down event.  A key is "registered" when it is
107 // pressed and then released, with no other keypresses or releases in
108 // between.  Registered keys are passed to CheckKey() to see if it
109 // should trigger a visibility toggle, an immediate reboot, or be
110 // queued to be processed next time the foreground thread wants a key
111 // (eg, for the menu).
112 //
113 // We also keep track of which keys are currently down so that
114 // CheckKey can call IsKeyPressed to see what other keys are held when
115 // a key is registered.
116 //
117 // updown == 1 for key down events; 0 for key up events
process_key(int key_code,int updown)118 void RecoveryUI::process_key(int key_code, int updown) {
119     bool register_key = false;
120     bool long_press = false;
121     bool reboot_enabled;
122 
123     pthread_mutex_lock(&key_queue_mutex);
124     key_pressed[key_code] = updown;
125     if (updown) {
126         ++key_down_count;
127         key_last_down = key_code;
128         key_long_press = false;
129         pthread_t th;
130         key_timer_t* info = new key_timer_t;
131         info->ui = this;
132         info->key_code = key_code;
133         info->count = key_down_count;
134         pthread_create(&th, NULL, &RecoveryUI::time_key_helper, info);
135         pthread_detach(th);
136     } else {
137         if (key_last_down == key_code) {
138             long_press = key_long_press;
139             register_key = true;
140         }
141         key_last_down = -1;
142     }
143     reboot_enabled = enable_reboot;
144     pthread_mutex_unlock(&key_queue_mutex);
145 
146     if (register_key) {
147         NextCheckKeyIsLong(long_press);
148         switch (CheckKey(key_code)) {
149           case RecoveryUI::IGNORE:
150             break;
151 
152           case RecoveryUI::TOGGLE:
153             ShowText(!IsTextVisible());
154             break;
155 
156           case RecoveryUI::REBOOT:
157             if (reboot_enabled) {
158                 android_reboot(ANDROID_RB_RESTART, 0, 0);
159             }
160             break;
161 
162           case RecoveryUI::ENQUEUE:
163             EnqueueKey(key_code);
164             break;
165 
166           case RecoveryUI::MOUNT_SYSTEM:
167 #ifndef NO_RECOVERY_MOUNT
168             ensure_path_mounted("/system");
169             Print("Mounted /system.");
170 #endif
171             break;
172         }
173     }
174 }
175 
time_key_helper(void * cookie)176 void* RecoveryUI::time_key_helper(void* cookie) {
177     key_timer_t* info = (key_timer_t*) cookie;
178     info->ui->time_key(info->key_code, info->count);
179     delete info;
180     return NULL;
181 }
182 
time_key(int key_code,int count)183 void RecoveryUI::time_key(int key_code, int count) {
184     usleep(750000);  // 750 ms == "long"
185     bool long_press = false;
186     pthread_mutex_lock(&key_queue_mutex);
187     if (key_last_down == key_code && key_down_count == count) {
188         long_press = key_long_press = true;
189     }
190     pthread_mutex_unlock(&key_queue_mutex);
191     if (long_press) KeyLongPress(key_code);
192 }
193 
EnqueueKey(int key_code)194 void RecoveryUI::EnqueueKey(int key_code) {
195     pthread_mutex_lock(&key_queue_mutex);
196     const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]);
197     if (key_queue_len < queue_max) {
198         key_queue[key_queue_len++] = key_code;
199         pthread_cond_signal(&key_queue_cond);
200     }
201     pthread_mutex_unlock(&key_queue_mutex);
202 }
203 
204 
205 // Reads input events, handles special hot keys, and adds to the key queue.
input_thread(void * cookie)206 void* RecoveryUI::input_thread(void *cookie)
207 {
208     for (;;) {
209         if (!ev_wait(-1))
210             ev_dispatch();
211     }
212     return NULL;
213 }
214 
WaitKey()215 int RecoveryUI::WaitKey()
216 {
217     pthread_mutex_lock(&key_queue_mutex);
218 
219     // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is
220     // plugged in.
221     do {
222         struct timeval now;
223         struct timespec timeout;
224         gettimeofday(&now, NULL);
225         timeout.tv_sec = now.tv_sec;
226         timeout.tv_nsec = now.tv_usec * 1000;
227         timeout.tv_sec += UI_WAIT_KEY_TIMEOUT_SEC;
228 
229         int rc = 0;
230         while (key_queue_len == 0 && rc != ETIMEDOUT) {
231             rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex,
232                                         &timeout);
233         }
234     } while (usb_connected() && key_queue_len == 0);
235 
236     int key = -1;
237     if (key_queue_len > 0) {
238         key = key_queue[0];
239         memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len);
240     }
241     pthread_mutex_unlock(&key_queue_mutex);
242     return key;
243 }
244 
245 // Return true if USB is connected.
usb_connected()246 bool RecoveryUI::usb_connected() {
247     int fd = open("/sys/class/android_usb/android0/state", O_RDONLY);
248     if (fd < 0) {
249         printf("failed to open /sys/class/android_usb/android0/state: %s\n",
250                strerror(errno));
251         return 0;
252     }
253 
254     char buf;
255     /* USB is connected if android_usb state is CONNECTED or CONFIGURED */
256     int connected = (read(fd, &buf, 1) == 1) && (buf == 'C');
257     if (close(fd) < 0) {
258         printf("failed to close /sys/class/android_usb/android0/state: %s\n",
259                strerror(errno));
260     }
261     return connected;
262 }
263 
IsKeyPressed(int key)264 bool RecoveryUI::IsKeyPressed(int key)
265 {
266     pthread_mutex_lock(&key_queue_mutex);
267     int pressed = key_pressed[key];
268     pthread_mutex_unlock(&key_queue_mutex);
269     return pressed;
270 }
271 
FlushKeys()272 void RecoveryUI::FlushKeys() {
273     pthread_mutex_lock(&key_queue_mutex);
274     key_queue_len = 0;
275     pthread_mutex_unlock(&key_queue_mutex);
276 }
277 
278 // The default CheckKey implementation assumes the device has power,
279 // volume up, and volume down keys.
280 //
281 // - Hold power and press vol-up to toggle display.
282 // - Press power seven times in a row to reboot.
283 // - Alternate vol-up and vol-down seven times to mount /system.
CheckKey(int key)284 RecoveryUI::KeyAction RecoveryUI::CheckKey(int key) {
285     if ((IsKeyPressed(KEY_POWER) && key == KEY_VOLUMEUP) || key == KEY_HOME) {
286         return TOGGLE;
287     }
288 
289     if (key == KEY_POWER) {
290         pthread_mutex_lock(&key_queue_mutex);
291         bool reboot_enabled = enable_reboot;
292         pthread_mutex_unlock(&key_queue_mutex);
293 
294         if (reboot_enabled) {
295             ++consecutive_power_keys;
296             if (consecutive_power_keys >= 7) {
297                 return REBOOT;
298             }
299         }
300     } else {
301         consecutive_power_keys = 0;
302     }
303 
304     if ((key == KEY_VOLUMEUP &&
305          (last_key == KEY_VOLUMEDOWN || last_key == -1)) ||
306         (key == KEY_VOLUMEDOWN &&
307          (last_key == KEY_VOLUMEUP || last_key == -1))) {
308         ++consecutive_alternate_keys;
309         if (consecutive_alternate_keys >= 7) {
310             consecutive_alternate_keys = 0;
311             return MOUNT_SYSTEM;
312         }
313     } else {
314         consecutive_alternate_keys = 0;
315     }
316     last_key = key;
317 
318     return ENQUEUE;
319 }
320 
NextCheckKeyIsLong(bool is_long_press)321 void RecoveryUI::NextCheckKeyIsLong(bool is_long_press) {
322 }
323 
KeyLongPress(int key)324 void RecoveryUI::KeyLongPress(int key) {
325 }
326 
SetEnableReboot(bool enabled)327 void RecoveryUI::SetEnableReboot(bool enabled) {
328     pthread_mutex_lock(&key_queue_mutex);
329     enable_reboot = enabled;
330     pthread_mutex_unlock(&key_queue_mutex);
331 }
332