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