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 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 62 void RecoveryUI::Init() { 63 ev_init(input_callback, NULL); 64 pthread_create(&input_t, NULL, input_thread, NULL); 65 } 66 67 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 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 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 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 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. 206 void* RecoveryUI::input_thread(void *cookie) 207 { 208 for (;;) { 209 if (!ev_wait(-1)) 210 ev_dispatch(); 211 } 212 return NULL; 213 } 214 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. 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 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 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. 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 321 void RecoveryUI::NextCheckKeyIsLong(bool is_long_press) { 322 } 323 324 void RecoveryUI::KeyLongPress(int key) { 325 } 326 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