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/properties.h> 32 #include <cutils/android_reboot.h> 33 34 #include "common.h" 35 #include "roots.h" 36 #include "device.h" 37 #include "minui/minui.h" 38 #include "screen_ui.h" 39 #include "ui.h" 40 41 #define UI_WAIT_KEY_TIMEOUT_SEC 120 42 43 RecoveryUI::RecoveryUI() 44 : key_queue_len(0), 45 key_last_down(-1), 46 key_long_press(false), 47 key_down_count(0), 48 enable_reboot(true), 49 consecutive_power_keys(0), 50 last_key(-1), 51 has_power_key(false), 52 has_up_key(false), 53 has_down_key(false) { 54 pthread_mutex_init(&key_queue_mutex, nullptr); 55 pthread_cond_init(&key_queue_cond, nullptr); 56 memset(key_pressed, 0, sizeof(key_pressed)); 57 } 58 59 void RecoveryUI::OnKeyDetected(int key_code) { 60 if (key_code == KEY_POWER) { 61 has_power_key = true; 62 } else if (key_code == KEY_DOWN || key_code == KEY_VOLUMEDOWN) { 63 has_down_key = true; 64 } else if (key_code == KEY_UP || key_code == KEY_VOLUMEUP) { 65 has_up_key = true; 66 } 67 } 68 69 int RecoveryUI::InputCallback(int fd, uint32_t epevents, void* data) { 70 return reinterpret_cast<RecoveryUI*>(data)->OnInputEvent(fd, epevents); 71 } 72 73 // Reads input events, handles special hot keys, and adds to the key queue. 74 static void* InputThreadLoop(void*) { 75 while (true) { 76 if (!ev_wait(-1)) { 77 ev_dispatch(); 78 } 79 } 80 return nullptr; 81 } 82 83 void RecoveryUI::Init() { 84 ev_init(InputCallback, this); 85 86 ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1)); 87 88 pthread_create(&input_thread_, nullptr, InputThreadLoop, nullptr); 89 } 90 91 int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) { 92 struct input_event ev; 93 if (ev_get_input(fd, epevents, &ev) == -1) { 94 return -1; 95 } 96 97 if (ev.type == EV_SYN) { 98 return 0; 99 } else if (ev.type == EV_REL) { 100 if (ev.code == REL_Y) { 101 // accumulate the up or down motion reported by 102 // the trackball. When it exceeds a threshold 103 // (positive or negative), fake an up/down 104 // key event. 105 rel_sum += ev.value; 106 if (rel_sum > 3) { 107 ProcessKey(KEY_DOWN, 1); // press down key 108 ProcessKey(KEY_DOWN, 0); // and release it 109 rel_sum = 0; 110 } else if (rel_sum < -3) { 111 ProcessKey(KEY_UP, 1); // press up key 112 ProcessKey(KEY_UP, 0); // and release it 113 rel_sum = 0; 114 } 115 } 116 } else { 117 rel_sum = 0; 118 } 119 120 if (ev.type == EV_KEY && ev.code <= KEY_MAX) { 121 ProcessKey(ev.code, ev.value); 122 } 123 124 return 0; 125 } 126 127 // Process a key-up or -down event. A key is "registered" when it is 128 // pressed and then released, with no other keypresses or releases in 129 // between. Registered keys are passed to CheckKey() to see if it 130 // should trigger a visibility toggle, an immediate reboot, or be 131 // queued to be processed next time the foreground thread wants a key 132 // (eg, for the menu). 133 // 134 // We also keep track of which keys are currently down so that 135 // CheckKey can call IsKeyPressed to see what other keys are held when 136 // a key is registered. 137 // 138 // updown == 1 for key down events; 0 for key up events 139 void RecoveryUI::ProcessKey(int key_code, int updown) { 140 bool register_key = false; 141 bool long_press = false; 142 bool reboot_enabled; 143 144 pthread_mutex_lock(&key_queue_mutex); 145 key_pressed[key_code] = updown; 146 if (updown) { 147 ++key_down_count; 148 key_last_down = key_code; 149 key_long_press = false; 150 key_timer_t* info = new key_timer_t; 151 info->ui = this; 152 info->key_code = key_code; 153 info->count = key_down_count; 154 pthread_t thread; 155 pthread_create(&thread, nullptr, &RecoveryUI::time_key_helper, info); 156 pthread_detach(thread); 157 } else { 158 if (key_last_down == key_code) { 159 long_press = key_long_press; 160 register_key = true; 161 } 162 key_last_down = -1; 163 } 164 reboot_enabled = enable_reboot; 165 pthread_mutex_unlock(&key_queue_mutex); 166 167 if (register_key) { 168 switch (CheckKey(key_code, long_press)) { 169 case RecoveryUI::IGNORE: 170 break; 171 172 case RecoveryUI::TOGGLE: 173 ShowText(!IsTextVisible()); 174 break; 175 176 case RecoveryUI::REBOOT: 177 if (reboot_enabled) { 178 property_set(ANDROID_RB_PROPERTY, "reboot,"); 179 while (true) { pause(); } 180 } 181 break; 182 183 case RecoveryUI::ENQUEUE: 184 EnqueueKey(key_code); 185 break; 186 } 187 } 188 } 189 190 void* RecoveryUI::time_key_helper(void* cookie) { 191 key_timer_t* info = (key_timer_t*) cookie; 192 info->ui->time_key(info->key_code, info->count); 193 delete info; 194 return nullptr; 195 } 196 197 void RecoveryUI::time_key(int key_code, int count) { 198 usleep(750000); // 750 ms == "long" 199 bool long_press = false; 200 pthread_mutex_lock(&key_queue_mutex); 201 if (key_last_down == key_code && key_down_count == count) { 202 long_press = key_long_press = true; 203 } 204 pthread_mutex_unlock(&key_queue_mutex); 205 if (long_press) KeyLongPress(key_code); 206 } 207 208 void RecoveryUI::EnqueueKey(int key_code) { 209 pthread_mutex_lock(&key_queue_mutex); 210 const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]); 211 if (key_queue_len < queue_max) { 212 key_queue[key_queue_len++] = key_code; 213 pthread_cond_signal(&key_queue_cond); 214 } 215 pthread_mutex_unlock(&key_queue_mutex); 216 } 217 218 int RecoveryUI::WaitKey() { 219 pthread_mutex_lock(&key_queue_mutex); 220 221 // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is 222 // plugged in. 223 do { 224 struct timeval now; 225 struct timespec timeout; 226 gettimeofday(&now, nullptr); 227 timeout.tv_sec = now.tv_sec; 228 timeout.tv_nsec = now.tv_usec * 1000; 229 timeout.tv_sec += UI_WAIT_KEY_TIMEOUT_SEC; 230 231 int rc = 0; 232 while (key_queue_len == 0 && rc != ETIMEDOUT) { 233 rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, &timeout); 234 } 235 } while (IsUsbConnected() && key_queue_len == 0); 236 237 int key = -1; 238 if (key_queue_len > 0) { 239 key = key_queue[0]; 240 memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len); 241 } 242 pthread_mutex_unlock(&key_queue_mutex); 243 return key; 244 } 245 246 bool RecoveryUI::IsUsbConnected() { 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 = (TEMP_FAILURE_RETRY(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 pthread_mutex_lock(&key_queue_mutex); 266 int pressed = key_pressed[key]; 267 pthread_mutex_unlock(&key_queue_mutex); 268 return pressed; 269 } 270 271 bool RecoveryUI::IsLongPress() { 272 pthread_mutex_lock(&key_queue_mutex); 273 bool result = key_long_press; 274 pthread_mutex_unlock(&key_queue_mutex); 275 return result; 276 } 277 278 bool RecoveryUI::HasThreeButtons() { 279 return has_power_key && has_up_key && has_down_key; 280 } 281 282 void RecoveryUI::FlushKeys() { 283 pthread_mutex_lock(&key_queue_mutex); 284 key_queue_len = 0; 285 pthread_mutex_unlock(&key_queue_mutex); 286 } 287 288 RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) { 289 pthread_mutex_lock(&key_queue_mutex); 290 key_long_press = false; 291 pthread_mutex_unlock(&key_queue_mutex); 292 293 // If we have power and volume up keys, that chord is the signal to toggle the text display. 294 if (HasThreeButtons()) { 295 if (key == KEY_VOLUMEUP && IsKeyPressed(KEY_POWER)) { 296 return TOGGLE; 297 } 298 } else { 299 // Otherwise long press of any button toggles to the text display, 300 // and there's no way to toggle back (but that's pretty useless anyway). 301 if (is_long_press && !IsTextVisible()) { 302 return TOGGLE; 303 } 304 305 // Also, for button-limited devices, a long press is translated to KEY_ENTER. 306 if (is_long_press && IsTextVisible()) { 307 EnqueueKey(KEY_ENTER); 308 return IGNORE; 309 } 310 } 311 312 // Press power seven times in a row to reboot. 313 if (key == KEY_POWER) { 314 pthread_mutex_lock(&key_queue_mutex); 315 bool reboot_enabled = enable_reboot; 316 pthread_mutex_unlock(&key_queue_mutex); 317 318 if (reboot_enabled) { 319 ++consecutive_power_keys; 320 if (consecutive_power_keys >= 7) { 321 return REBOOT; 322 } 323 } 324 } else { 325 consecutive_power_keys = 0; 326 } 327 328 last_key = key; 329 return IsTextVisible() ? ENQUEUE : IGNORE; 330 } 331 332 void RecoveryUI::KeyLongPress(int) { 333 } 334 335 void RecoveryUI::SetEnableReboot(bool enabled) { 336 pthread_mutex_lock(&key_queue_mutex); 337 enable_reboot = enabled; 338 pthread_mutex_unlock(&key_queue_mutex); 339 } 340