1 /* 2 * Copyright (C) 2007 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 #define LOG_NDEBUG 0 18 #define LOG_TAG "BootAnimation" 19 20 #include <stdint.h> 21 #include <inttypes.h> 22 #include <sys/inotify.h> 23 #include <sys/poll.h> 24 #include <sys/stat.h> 25 #include <sys/types.h> 26 #include <math.h> 27 #include <fcntl.h> 28 #include <utils/misc.h> 29 #include <signal.h> 30 #include <time.h> 31 32 #include <cutils/atomic.h> 33 #include <cutils/properties.h> 34 35 #include <androidfw/AssetManager.h> 36 #include <binder/IPCThreadState.h> 37 #include <utils/Errors.h> 38 #include <utils/Log.h> 39 #include <utils/SystemClock.h> 40 41 #include <android-base/properties.h> 42 43 #include <ui/PixelFormat.h> 44 #include <ui/Rect.h> 45 #include <ui/Region.h> 46 #include <ui/DisplayInfo.h> 47 48 #include <gui/ISurfaceComposer.h> 49 #include <gui/Surface.h> 50 #include <gui/SurfaceComposerClient.h> 51 52 // TODO: Fix Skia. 53 #pragma GCC diagnostic push 54 #pragma GCC diagnostic ignored "-Wunused-parameter" 55 #include <SkBitmap.h> 56 #include <SkImage.h> 57 #include <SkStream.h> 58 #pragma GCC diagnostic pop 59 60 #include <GLES/gl.h> 61 #include <GLES/glext.h> 62 #include <EGL/eglext.h> 63 64 #include "BootAnimation.h" 65 66 namespace android { 67 68 static const char OEM_BOOTANIMATION_FILE[] = "/oem/media/bootanimation.zip"; 69 static const char PRODUCT_BOOTANIMATION_FILE[] = "/product/media/bootanimation.zip"; 70 static const char SYSTEM_BOOTANIMATION_FILE[] = "/system/media/bootanimation.zip"; 71 static const char PRODUCT_ENCRYPTED_BOOTANIMATION_FILE[] = "/product/media/bootanimation-encrypted.zip"; 72 static const char SYSTEM_ENCRYPTED_BOOTANIMATION_FILE[] = "/system/media/bootanimation-encrypted.zip"; 73 static const char OEM_SHUTDOWNANIMATION_FILE[] = "/oem/media/shutdownanimation.zip"; 74 static const char PRODUCT_SHUTDOWNANIMATION_FILE[] = "/product/media/shutdownanimation.zip"; 75 static const char SYSTEM_SHUTDOWNANIMATION_FILE[] = "/system/media/shutdownanimation.zip"; 76 77 static const char SYSTEM_DATA_DIR_PATH[] = "/data/system"; 78 static const char SYSTEM_TIME_DIR_NAME[] = "time"; 79 static const char SYSTEM_TIME_DIR_PATH[] = "/data/system/time"; 80 static const char CLOCK_FONT_ASSET[] = "images/clock_font.png"; 81 static const char CLOCK_FONT_ZIP_NAME[] = "clock_font.png"; 82 static const char LAST_TIME_CHANGED_FILE_NAME[] = "last_time_change"; 83 static const char LAST_TIME_CHANGED_FILE_PATH[] = "/data/system/time/last_time_change"; 84 static const char ACCURATE_TIME_FLAG_FILE_NAME[] = "time_is_accurate"; 85 static const char ACCURATE_TIME_FLAG_FILE_PATH[] = "/data/system/time/time_is_accurate"; 86 static const char TIME_FORMAT_12_HOUR_FLAG_FILE_PATH[] = "/data/system/time/time_format_12_hour"; 87 // Java timestamp format. Don't show the clock if the date is before 2000-01-01 00:00:00. 88 static const long long ACCURATE_TIME_EPOCH = 946684800000; 89 static constexpr char FONT_BEGIN_CHAR = ' '; 90 static constexpr char FONT_END_CHAR = '~' + 1; 91 static constexpr size_t FONT_NUM_CHARS = FONT_END_CHAR - FONT_BEGIN_CHAR + 1; 92 static constexpr size_t FONT_NUM_COLS = 16; 93 static constexpr size_t FONT_NUM_ROWS = FONT_NUM_CHARS / FONT_NUM_COLS; 94 static const int TEXT_CENTER_VALUE = INT_MAX; 95 static const int TEXT_MISSING_VALUE = INT_MIN; 96 static const char EXIT_PROP_NAME[] = "service.bootanim.exit"; 97 static const int ANIM_ENTRY_NAME_MAX = 256; 98 static constexpr size_t TEXT_POS_LEN_MAX = 16; 99 100 // --------------------------------------------------------------------------- 101 102 BootAnimation::BootAnimation(sp<Callbacks> callbacks) 103 : Thread(false), mClockEnabled(true), mTimeIsAccurate(false), 104 mTimeFormat12Hour(false), mTimeCheckThread(NULL), mCallbacks(callbacks) { 105 mSession = new SurfaceComposerClient(); 106 107 std::string powerCtl = android::base::GetProperty("sys.powerctl", ""); 108 if (powerCtl.empty()) { 109 mShuttingDown = false; 110 } else { 111 mShuttingDown = true; 112 } 113 } 114 115 void BootAnimation::onFirstRef() { 116 status_t err = mSession->linkToComposerDeath(this); 117 ALOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err)); 118 if (err == NO_ERROR) { 119 run("BootAnimation", PRIORITY_DISPLAY); 120 } 121 } 122 123 sp<SurfaceComposerClient> BootAnimation::session() const { 124 return mSession; 125 } 126 127 128 void BootAnimation::binderDied(const wp<IBinder>&) 129 { 130 // woah, surfaceflinger died! 131 ALOGD("SurfaceFlinger died, exiting..."); 132 133 // calling requestExit() is not enough here because the Surface code 134 // might be blocked on a condition variable that will never be updated. 135 kill( getpid(), SIGKILL ); 136 requestExit(); 137 } 138 139 status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets, 140 const char* name) { 141 Asset* asset = assets.open(name, Asset::ACCESS_BUFFER); 142 if (asset == NULL) 143 return NO_INIT; 144 SkBitmap bitmap; 145 sk_sp<SkData> data = SkData::MakeWithoutCopy(asset->getBuffer(false), 146 asset->getLength()); 147 sk_sp<SkImage> image = SkImage::MakeFromEncoded(data); 148 image->asLegacyBitmap(&bitmap, SkImage::kRO_LegacyBitmapMode); 149 asset->close(); 150 delete asset; 151 152 const int w = bitmap.width(); 153 const int h = bitmap.height(); 154 const void* p = bitmap.getPixels(); 155 156 GLint crop[4] = { 0, h, w, -h }; 157 texture->w = w; 158 texture->h = h; 159 160 glGenTextures(1, &texture->name); 161 glBindTexture(GL_TEXTURE_2D, texture->name); 162 163 switch (bitmap.colorType()) { 164 case kAlpha_8_SkColorType: 165 glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA, 166 GL_UNSIGNED_BYTE, p); 167 break; 168 case kARGB_4444_SkColorType: 169 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, 170 GL_UNSIGNED_SHORT_4_4_4_4, p); 171 break; 172 case kN32_SkColorType: 173 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, 174 GL_UNSIGNED_BYTE, p); 175 break; 176 case kRGB_565_SkColorType: 177 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, 178 GL_UNSIGNED_SHORT_5_6_5, p); 179 break; 180 default: 181 break; 182 } 183 184 glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop); 185 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 186 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 187 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 188 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 189 190 return NO_ERROR; 191 } 192 193 status_t BootAnimation::initTexture(FileMap* map, int* width, int* height) 194 { 195 SkBitmap bitmap; 196 sk_sp<SkData> data = SkData::MakeWithoutCopy(map->getDataPtr(), 197 map->getDataLength()); 198 sk_sp<SkImage> image = SkImage::MakeFromEncoded(data); 199 image->asLegacyBitmap(&bitmap, SkImage::kRO_LegacyBitmapMode); 200 201 // FileMap memory is never released until application exit. 202 // Release it now as the texture is already loaded and the memory used for 203 // the packed resource can be released. 204 delete map; 205 206 const int w = bitmap.width(); 207 const int h = bitmap.height(); 208 const void* p = bitmap.getPixels(); 209 210 GLint crop[4] = { 0, h, w, -h }; 211 int tw = 1 << (31 - __builtin_clz(w)); 212 int th = 1 << (31 - __builtin_clz(h)); 213 if (tw < w) tw <<= 1; 214 if (th < h) th <<= 1; 215 216 switch (bitmap.colorType()) { 217 case kN32_SkColorType: 218 if (!mUseNpotTextures && (tw != w || th != h)) { 219 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tw, th, 0, GL_RGBA, 220 GL_UNSIGNED_BYTE, 0); 221 glTexSubImage2D(GL_TEXTURE_2D, 0, 222 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, p); 223 } else { 224 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, 225 GL_UNSIGNED_BYTE, p); 226 } 227 break; 228 229 case kRGB_565_SkColorType: 230 if (!mUseNpotTextures && (tw != w || th != h)) { 231 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, tw, th, 0, GL_RGB, 232 GL_UNSIGNED_SHORT_5_6_5, 0); 233 glTexSubImage2D(GL_TEXTURE_2D, 0, 234 0, 0, w, h, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, p); 235 } else { 236 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, 237 GL_UNSIGNED_SHORT_5_6_5, p); 238 } 239 break; 240 default: 241 break; 242 } 243 244 glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop); 245 246 *width = w; 247 *height = h; 248 249 return NO_ERROR; 250 } 251 252 status_t BootAnimation::readyToRun() { 253 mAssets.addDefaultAssets(); 254 255 sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay( 256 ISurfaceComposer::eDisplayIdMain)); 257 DisplayInfo dinfo; 258 status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &dinfo); 259 if (status) 260 return -1; 261 262 // create the native surface 263 sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"), 264 dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565); 265 266 SurfaceComposerClient::Transaction t; 267 t.setLayer(control, 0x40000000) 268 .apply(); 269 270 sp<Surface> s = control->getSurface(); 271 272 // initialize opengl and egl 273 const EGLint attribs[] = { 274 EGL_RED_SIZE, 8, 275 EGL_GREEN_SIZE, 8, 276 EGL_BLUE_SIZE, 8, 277 EGL_DEPTH_SIZE, 0, 278 EGL_NONE 279 }; 280 EGLint w, h; 281 EGLint numConfigs; 282 EGLConfig config; 283 EGLSurface surface; 284 EGLContext context; 285 286 EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); 287 288 eglInitialize(display, 0, 0); 289 eglChooseConfig(display, attribs, &config, 1, &numConfigs); 290 surface = eglCreateWindowSurface(display, config, s.get(), NULL); 291 context = eglCreateContext(display, config, NULL, NULL); 292 eglQuerySurface(display, surface, EGL_WIDTH, &w); 293 eglQuerySurface(display, surface, EGL_HEIGHT, &h); 294 295 if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) 296 return NO_INIT; 297 298 mDisplay = display; 299 mContext = context; 300 mSurface = surface; 301 mWidth = w; 302 mHeight = h; 303 mFlingerSurfaceControl = control; 304 mFlingerSurface = s; 305 306 // If the device has encryption turned on or is in process 307 // of being encrypted we show the encrypted boot animation. 308 char decrypt[PROPERTY_VALUE_MAX]; 309 property_get("vold.decrypt", decrypt, ""); 310 311 bool encryptedAnimation = atoi(decrypt) != 0 || 312 !strcmp("trigger_restart_min_framework", decrypt); 313 314 if (!mShuttingDown && encryptedAnimation) { 315 static const char* encryptedBootFiles[] = 316 {PRODUCT_ENCRYPTED_BOOTANIMATION_FILE, SYSTEM_ENCRYPTED_BOOTANIMATION_FILE}; 317 for (const char* f : encryptedBootFiles) { 318 if (access(f, R_OK) == 0) { 319 mZipFileName = f; 320 return NO_ERROR; 321 } 322 } 323 } 324 static const char* bootFiles[] = 325 {PRODUCT_BOOTANIMATION_FILE, OEM_BOOTANIMATION_FILE, SYSTEM_BOOTANIMATION_FILE}; 326 static const char* shutdownFiles[] = 327 {PRODUCT_SHUTDOWNANIMATION_FILE, OEM_SHUTDOWNANIMATION_FILE, SYSTEM_SHUTDOWNANIMATION_FILE}; 328 329 for (const char* f : (!mShuttingDown ? bootFiles : shutdownFiles)) { 330 if (access(f, R_OK) == 0) { 331 mZipFileName = f; 332 return NO_ERROR; 333 } 334 } 335 return NO_ERROR; 336 } 337 338 bool BootAnimation::threadLoop() 339 { 340 bool r; 341 // We have no bootanimation file, so we use the stock android logo 342 // animation. 343 if (mZipFileName.isEmpty()) { 344 r = android(); 345 } else { 346 r = movie(); 347 } 348 349 eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); 350 eglDestroyContext(mDisplay, mContext); 351 eglDestroySurface(mDisplay, mSurface); 352 mFlingerSurface.clear(); 353 mFlingerSurfaceControl.clear(); 354 eglTerminate(mDisplay); 355 eglReleaseThread(); 356 IPCThreadState::self()->stopProcess(); 357 return r; 358 } 359 360 bool BootAnimation::android() 361 { 362 ALOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot", 363 elapsedRealtime()); 364 initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png"); 365 initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png"); 366 367 mCallbacks->init({}); 368 369 // clear screen 370 glShadeModel(GL_FLAT); 371 glDisable(GL_DITHER); 372 glDisable(GL_SCISSOR_TEST); 373 glClearColor(0,0,0,1); 374 glClear(GL_COLOR_BUFFER_BIT); 375 eglSwapBuffers(mDisplay, mSurface); 376 377 glEnable(GL_TEXTURE_2D); 378 glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); 379 380 const GLint xc = (mWidth - mAndroid[0].w) / 2; 381 const GLint yc = (mHeight - mAndroid[0].h) / 2; 382 const Rect updateRect(xc, yc, xc + mAndroid[0].w, yc + mAndroid[0].h); 383 384 glScissor(updateRect.left, mHeight - updateRect.bottom, updateRect.width(), 385 updateRect.height()); 386 387 // Blend state 388 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 389 glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); 390 391 const nsecs_t startTime = systemTime(); 392 do { 393 nsecs_t now = systemTime(); 394 double time = now - startTime; 395 float t = 4.0f * float(time / us2ns(16667)) / mAndroid[1].w; 396 GLint offset = (1 - (t - floorf(t))) * mAndroid[1].w; 397 GLint x = xc - offset; 398 399 glDisable(GL_SCISSOR_TEST); 400 glClear(GL_COLOR_BUFFER_BIT); 401 402 glEnable(GL_SCISSOR_TEST); 403 glDisable(GL_BLEND); 404 glBindTexture(GL_TEXTURE_2D, mAndroid[1].name); 405 glDrawTexiOES(x, yc, 0, mAndroid[1].w, mAndroid[1].h); 406 glDrawTexiOES(x + mAndroid[1].w, yc, 0, mAndroid[1].w, mAndroid[1].h); 407 408 glEnable(GL_BLEND); 409 glBindTexture(GL_TEXTURE_2D, mAndroid[0].name); 410 glDrawTexiOES(xc, yc, 0, mAndroid[0].w, mAndroid[0].h); 411 412 EGLBoolean res = eglSwapBuffers(mDisplay, mSurface); 413 if (res == EGL_FALSE) 414 break; 415 416 // 12fps: don't animate too fast to preserve CPU 417 const nsecs_t sleepTime = 83333 - ns2us(systemTime() - now); 418 if (sleepTime > 0) 419 usleep(sleepTime); 420 421 checkExit(); 422 } while (!exitPending()); 423 424 glDeleteTextures(1, &mAndroid[0].name); 425 glDeleteTextures(1, &mAndroid[1].name); 426 return false; 427 } 428 429 void BootAnimation::checkExit() { 430 // Allow surface flinger to gracefully request shutdown 431 char value[PROPERTY_VALUE_MAX]; 432 property_get(EXIT_PROP_NAME, value, "0"); 433 int exitnow = atoi(value); 434 if (exitnow) { 435 requestExit(); 436 mCallbacks->shutdown(); 437 } 438 } 439 440 bool BootAnimation::validClock(const Animation::Part& part) { 441 return part.clockPosX != TEXT_MISSING_VALUE && part.clockPosY != TEXT_MISSING_VALUE; 442 } 443 444 bool parseTextCoord(const char* str, int* dest) { 445 if (strcmp("c", str) == 0) { 446 *dest = TEXT_CENTER_VALUE; 447 return true; 448 } 449 450 char* end; 451 int val = (int) strtol(str, &end, 0); 452 if (end == str || *end != '\0' || val == INT_MAX || val == INT_MIN) { 453 return false; 454 } 455 *dest = val; 456 return true; 457 } 458 459 // Parse two position coordinates. If only string is non-empty, treat it as the y value. 460 void parsePosition(const char* str1, const char* str2, int* x, int* y) { 461 bool success = false; 462 if (strlen(str1) == 0) { // No values were specified 463 // success = false 464 } else if (strlen(str2) == 0) { // we have only one value 465 if (parseTextCoord(str1, y)) { 466 *x = TEXT_CENTER_VALUE; 467 success = true; 468 } 469 } else { 470 if (parseTextCoord(str1, x) && parseTextCoord(str2, y)) { 471 success = true; 472 } 473 } 474 475 if (!success) { 476 *x = TEXT_MISSING_VALUE; 477 *y = TEXT_MISSING_VALUE; 478 } 479 } 480 481 // Parse a color represented as an HTML-style 'RRGGBB' string: each pair of 482 // characters in str is a hex number in [0, 255], which are converted to 483 // floating point values in the range [0.0, 1.0] and placed in the 484 // corresponding elements of color. 485 // 486 // If the input string isn't valid, parseColor returns false and color is 487 // left unchanged. 488 static bool parseColor(const char str[7], float color[3]) { 489 float tmpColor[3]; 490 for (int i = 0; i < 3; i++) { 491 int val = 0; 492 for (int j = 0; j < 2; j++) { 493 val *= 16; 494 char c = str[2*i + j]; 495 if (c >= '0' && c <= '9') val += c - '0'; 496 else if (c >= 'A' && c <= 'F') val += (c - 'A') + 10; 497 else if (c >= 'a' && c <= 'f') val += (c - 'a') + 10; 498 else return false; 499 } 500 tmpColor[i] = static_cast<float>(val) / 255.0f; 501 } 502 memcpy(color, tmpColor, sizeof(tmpColor)); 503 return true; 504 } 505 506 507 static bool readFile(ZipFileRO* zip, const char* name, String8& outString) 508 { 509 ZipEntryRO entry = zip->findEntryByName(name); 510 ALOGE_IF(!entry, "couldn't find %s", name); 511 if (!entry) { 512 return false; 513 } 514 515 FileMap* entryMap = zip->createEntryFileMap(entry); 516 zip->releaseEntry(entry); 517 ALOGE_IF(!entryMap, "entryMap is null"); 518 if (!entryMap) { 519 return false; 520 } 521 522 outString.setTo((char const*)entryMap->getDataPtr(), entryMap->getDataLength()); 523 delete entryMap; 524 return true; 525 } 526 527 // The font image should be a 96x2 array of character images. The 528 // columns are the printable ASCII characters 0x20 - 0x7f. The 529 // top row is regular text; the bottom row is bold. 530 status_t BootAnimation::initFont(Font* font, const char* fallback) { 531 status_t status = NO_ERROR; 532 533 if (font->map != nullptr) { 534 glGenTextures(1, &font->texture.name); 535 glBindTexture(GL_TEXTURE_2D, font->texture.name); 536 537 status = initTexture(font->map, &font->texture.w, &font->texture.h); 538 539 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 540 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 541 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 542 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 543 } else if (fallback != nullptr) { 544 status = initTexture(&font->texture, mAssets, fallback); 545 } else { 546 return NO_INIT; 547 } 548 549 if (status == NO_ERROR) { 550 font->char_width = font->texture.w / FONT_NUM_COLS; 551 font->char_height = font->texture.h / FONT_NUM_ROWS / 2; // There are bold and regular rows 552 } 553 554 return status; 555 } 556 557 void BootAnimation::drawText(const char* str, const Font& font, bool bold, int* x, int* y) { 558 glEnable(GL_BLEND); // Allow us to draw on top of the animation 559 glBindTexture(GL_TEXTURE_2D, font.texture.name); 560 561 const int len = strlen(str); 562 const int strWidth = font.char_width * len; 563 564 if (*x == TEXT_CENTER_VALUE) { 565 *x = (mWidth - strWidth) / 2; 566 } else if (*x < 0) { 567 *x = mWidth + *x - strWidth; 568 } 569 if (*y == TEXT_CENTER_VALUE) { 570 *y = (mHeight - font.char_height) / 2; 571 } else if (*y < 0) { 572 *y = mHeight + *y - font.char_height; 573 } 574 575 int cropRect[4] = { 0, 0, font.char_width, -font.char_height }; 576 577 for (int i = 0; i < len; i++) { 578 char c = str[i]; 579 580 if (c < FONT_BEGIN_CHAR || c > FONT_END_CHAR) { 581 c = '?'; 582 } 583 584 // Crop the texture to only the pixels in the current glyph 585 const int charPos = (c - FONT_BEGIN_CHAR); // Position in the list of valid characters 586 const int row = charPos / FONT_NUM_COLS; 587 const int col = charPos % FONT_NUM_COLS; 588 cropRect[0] = col * font.char_width; // Left of column 589 cropRect[1] = row * font.char_height * 2; // Top of row 590 // Move down to bottom of regular (one char_heigh) or bold (two char_heigh) line 591 cropRect[1] += bold ? 2 * font.char_height : font.char_height; 592 glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, cropRect); 593 594 glDrawTexiOES(*x, *y, 0, font.char_width, font.char_height); 595 596 *x += font.char_width; 597 } 598 599 glDisable(GL_BLEND); // Return to the animation's default behaviour 600 glBindTexture(GL_TEXTURE_2D, 0); 601 } 602 603 // We render 12 or 24 hour time. 604 void BootAnimation::drawClock(const Font& font, const int xPos, const int yPos) { 605 static constexpr char TIME_FORMAT_12[] = "%l:%M"; 606 static constexpr char TIME_FORMAT_24[] = "%H:%M"; 607 static constexpr int TIME_LENGTH = 6; 608 609 time_t rawtime; 610 time(&rawtime); 611 struct tm* timeInfo = localtime(&rawtime); 612 613 char timeBuff[TIME_LENGTH]; 614 const char* timeFormat = mTimeFormat12Hour ? TIME_FORMAT_12 : TIME_FORMAT_24; 615 size_t length = strftime(timeBuff, TIME_LENGTH, timeFormat, timeInfo); 616 617 if (length != TIME_LENGTH - 1) { 618 ALOGE("Couldn't format time; abandoning boot animation clock"); 619 mClockEnabled = false; 620 return; 621 } 622 623 char* out = timeBuff[0] == ' ' ? &timeBuff[1] : &timeBuff[0]; 624 int x = xPos; 625 int y = yPos; 626 drawText(out, font, false, &x, &y); 627 } 628 629 bool BootAnimation::parseAnimationDesc(Animation& animation) 630 { 631 String8 desString; 632 633 if (!readFile(animation.zip, "desc.txt", desString)) { 634 return false; 635 } 636 char const* s = desString.string(); 637 638 // Parse the description file 639 for (;;) { 640 const char* endl = strstr(s, "\n"); 641 if (endl == NULL) break; 642 String8 line(s, endl - s); 643 const char* l = line.string(); 644 int fps = 0; 645 int width = 0; 646 int height = 0; 647 int count = 0; 648 int pause = 0; 649 char path[ANIM_ENTRY_NAME_MAX]; 650 char color[7] = "000000"; // default to black if unspecified 651 char clockPos1[TEXT_POS_LEN_MAX + 1] = ""; 652 char clockPos2[TEXT_POS_LEN_MAX + 1] = ""; 653 654 char pathType; 655 if (sscanf(l, "%d %d %d", &width, &height, &fps) == 3) { 656 // ALOGD("> w=%d, h=%d, fps=%d", width, height, fps); 657 animation.width = width; 658 animation.height = height; 659 animation.fps = fps; 660 } else if (sscanf(l, " %c %d %d %s #%6s %16s %16s", 661 &pathType, &count, &pause, path, color, clockPos1, clockPos2) >= 4) { 662 //ALOGD("> type=%c, count=%d, pause=%d, path=%s, color=%s, clockPos1=%s, clockPos2=%s", 663 // pathType, count, pause, path, color, clockPos1, clockPos2); 664 Animation::Part part; 665 part.playUntilComplete = pathType == 'c'; 666 part.count = count; 667 part.pause = pause; 668 part.path = path; 669 part.audioData = NULL; 670 part.animation = NULL; 671 if (!parseColor(color, part.backgroundColor)) { 672 ALOGE("> invalid color '#%s'", color); 673 part.backgroundColor[0] = 0.0f; 674 part.backgroundColor[1] = 0.0f; 675 part.backgroundColor[2] = 0.0f; 676 } 677 parsePosition(clockPos1, clockPos2, &part.clockPosX, &part.clockPosY); 678 animation.parts.add(part); 679 } 680 else if (strcmp(l, "$SYSTEM") == 0) { 681 // ALOGD("> SYSTEM"); 682 Animation::Part part; 683 part.playUntilComplete = false; 684 part.count = 1; 685 part.pause = 0; 686 part.audioData = NULL; 687 part.animation = loadAnimation(String8(SYSTEM_BOOTANIMATION_FILE)); 688 if (part.animation != NULL) 689 animation.parts.add(part); 690 } 691 s = ++endl; 692 } 693 694 return true; 695 } 696 697 bool BootAnimation::preloadZip(Animation& animation) 698 { 699 // read all the data structures 700 const size_t pcount = animation.parts.size(); 701 void *cookie = NULL; 702 ZipFileRO* zip = animation.zip; 703 if (!zip->startIteration(&cookie)) { 704 return false; 705 } 706 707 ZipEntryRO entry; 708 char name[ANIM_ENTRY_NAME_MAX]; 709 while ((entry = zip->nextEntry(cookie)) != NULL) { 710 const int foundEntryName = zip->getEntryFileName(entry, name, ANIM_ENTRY_NAME_MAX); 711 if (foundEntryName > ANIM_ENTRY_NAME_MAX || foundEntryName == -1) { 712 ALOGE("Error fetching entry file name"); 713 continue; 714 } 715 716 const String8 entryName(name); 717 const String8 path(entryName.getPathDir()); 718 const String8 leaf(entryName.getPathLeaf()); 719 if (leaf.size() > 0) { 720 if (entryName == CLOCK_FONT_ZIP_NAME) { 721 FileMap* map = zip->createEntryFileMap(entry); 722 if (map) { 723 animation.clockFont.map = map; 724 } 725 continue; 726 } 727 728 for (size_t j = 0; j < pcount; j++) { 729 if (path == animation.parts[j].path) { 730 uint16_t method; 731 // supports only stored png files 732 if (zip->getEntryInfo(entry, &method, NULL, NULL, NULL, NULL, NULL)) { 733 if (method == ZipFileRO::kCompressStored) { 734 FileMap* map = zip->createEntryFileMap(entry); 735 if (map) { 736 Animation::Part& part(animation.parts.editItemAt(j)); 737 if (leaf == "audio.wav") { 738 // a part may have at most one audio file 739 part.audioData = (uint8_t *)map->getDataPtr(); 740 part.audioLength = map->getDataLength(); 741 } else if (leaf == "trim.txt") { 742 part.trimData.setTo((char const*)map->getDataPtr(), 743 map->getDataLength()); 744 } else { 745 Animation::Frame frame; 746 frame.name = leaf; 747 frame.map = map; 748 frame.trimWidth = animation.width; 749 frame.trimHeight = animation.height; 750 frame.trimX = 0; 751 frame.trimY = 0; 752 part.frames.add(frame); 753 } 754 } 755 } else { 756 ALOGE("bootanimation.zip is compressed; must be only stored"); 757 } 758 } 759 } 760 } 761 } 762 } 763 764 // If there is trimData present, override the positioning defaults. 765 for (Animation::Part& part : animation.parts) { 766 const char* trimDataStr = part.trimData.string(); 767 for (size_t frameIdx = 0; frameIdx < part.frames.size(); frameIdx++) { 768 const char* endl = strstr(trimDataStr, "\n"); 769 // No more trimData for this part. 770 if (endl == NULL) { 771 break; 772 } 773 String8 line(trimDataStr, endl - trimDataStr); 774 const char* lineStr = line.string(); 775 trimDataStr = ++endl; 776 int width = 0, height = 0, x = 0, y = 0; 777 if (sscanf(lineStr, "%dx%d+%d+%d", &width, &height, &x, &y) == 4) { 778 Animation::Frame& frame(part.frames.editItemAt(frameIdx)); 779 frame.trimWidth = width; 780 frame.trimHeight = height; 781 frame.trimX = x; 782 frame.trimY = y; 783 } else { 784 ALOGE("Error parsing trim.txt, line: %s", lineStr); 785 break; 786 } 787 } 788 } 789 790 mCallbacks->init(animation.parts); 791 792 zip->endIteration(cookie); 793 794 return true; 795 } 796 797 bool BootAnimation::movie() 798 { 799 Animation* animation = loadAnimation(mZipFileName); 800 if (animation == NULL) 801 return false; 802 803 bool anyPartHasClock = false; 804 for (size_t i=0; i < animation->parts.size(); i++) { 805 if(validClock(animation->parts[i])) { 806 anyPartHasClock = true; 807 break; 808 } 809 } 810 if (!anyPartHasClock) { 811 mClockEnabled = false; 812 } 813 814 // Check if npot textures are supported 815 mUseNpotTextures = false; 816 String8 gl_extensions; 817 const char* exts = reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)); 818 if (!exts) { 819 glGetError(); 820 } else { 821 gl_extensions.setTo(exts); 822 if ((gl_extensions.find("GL_ARB_texture_non_power_of_two") != -1) || 823 (gl_extensions.find("GL_OES_texture_npot") != -1)) { 824 mUseNpotTextures = true; 825 } 826 } 827 828 // Blend required to draw time on top of animation frames. 829 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 830 glShadeModel(GL_FLAT); 831 glDisable(GL_DITHER); 832 glDisable(GL_SCISSOR_TEST); 833 glDisable(GL_BLEND); 834 835 glBindTexture(GL_TEXTURE_2D, 0); 836 glEnable(GL_TEXTURE_2D); 837 glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); 838 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 839 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 840 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 841 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 842 843 bool clockFontInitialized = false; 844 if (mClockEnabled) { 845 clockFontInitialized = 846 (initFont(&animation->clockFont, CLOCK_FONT_ASSET) == NO_ERROR); 847 mClockEnabled = clockFontInitialized; 848 } 849 850 if (mClockEnabled && !updateIsTimeAccurate()) { 851 mTimeCheckThread = new TimeCheckThread(this); 852 mTimeCheckThread->run("BootAnimation::TimeCheckThread", PRIORITY_NORMAL); 853 } 854 855 playAnimation(*animation); 856 857 if (mTimeCheckThread != nullptr) { 858 mTimeCheckThread->requestExit(); 859 mTimeCheckThread = nullptr; 860 } 861 862 releaseAnimation(animation); 863 864 if (clockFontInitialized) { 865 glDeleteTextures(1, &animation->clockFont.texture.name); 866 } 867 868 return false; 869 } 870 871 bool BootAnimation::playAnimation(const Animation& animation) 872 { 873 const size_t pcount = animation.parts.size(); 874 nsecs_t frameDuration = s2ns(1) / animation.fps; 875 const int animationX = (mWidth - animation.width) / 2; 876 const int animationY = (mHeight - animation.height) / 2; 877 878 ALOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot", 879 elapsedRealtime()); 880 for (size_t i=0 ; i<pcount ; i++) { 881 const Animation::Part& part(animation.parts[i]); 882 const size_t fcount = part.frames.size(); 883 glBindTexture(GL_TEXTURE_2D, 0); 884 885 // Handle animation package 886 if (part.animation != NULL) { 887 playAnimation(*part.animation); 888 if (exitPending()) 889 break; 890 continue; //to next part 891 } 892 893 for (int r=0 ; !part.count || r<part.count ; r++) { 894 // Exit any non playuntil complete parts immediately 895 if(exitPending() && !part.playUntilComplete) 896 break; 897 898 mCallbacks->playPart(i, part, r); 899 900 glClearColor( 901 part.backgroundColor[0], 902 part.backgroundColor[1], 903 part.backgroundColor[2], 904 1.0f); 905 906 for (size_t j=0 ; j<fcount && (!exitPending() || part.playUntilComplete) ; j++) { 907 const Animation::Frame& frame(part.frames[j]); 908 nsecs_t lastFrame = systemTime(); 909 910 if (r > 0) { 911 glBindTexture(GL_TEXTURE_2D, frame.tid); 912 } else { 913 if (part.count != 1) { 914 glGenTextures(1, &frame.tid); 915 glBindTexture(GL_TEXTURE_2D, frame.tid); 916 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 917 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 918 } 919 int w, h; 920 initTexture(frame.map, &w, &h); 921 } 922 923 const int xc = animationX + frame.trimX; 924 const int yc = animationY + frame.trimY; 925 Region clearReg(Rect(mWidth, mHeight)); 926 clearReg.subtractSelf(Rect(xc, yc, xc+frame.trimWidth, yc+frame.trimHeight)); 927 if (!clearReg.isEmpty()) { 928 Region::const_iterator head(clearReg.begin()); 929 Region::const_iterator tail(clearReg.end()); 930 glEnable(GL_SCISSOR_TEST); 931 while (head != tail) { 932 const Rect& r2(*head++); 933 glScissor(r2.left, mHeight - r2.bottom, r2.width(), r2.height()); 934 glClear(GL_COLOR_BUFFER_BIT); 935 } 936 glDisable(GL_SCISSOR_TEST); 937 } 938 // specify the y center as ceiling((mHeight - frame.trimHeight) / 2) 939 // which is equivalent to mHeight - (yc + frame.trimHeight) 940 glDrawTexiOES(xc, mHeight - (yc + frame.trimHeight), 941 0, frame.trimWidth, frame.trimHeight); 942 if (mClockEnabled && mTimeIsAccurate && validClock(part)) { 943 drawClock(animation.clockFont, part.clockPosX, part.clockPosY); 944 } 945 946 eglSwapBuffers(mDisplay, mSurface); 947 948 nsecs_t now = systemTime(); 949 nsecs_t delay = frameDuration - (now - lastFrame); 950 //ALOGD("%lld, %lld", ns2ms(now - lastFrame), ns2ms(delay)); 951 lastFrame = now; 952 953 if (delay > 0) { 954 struct timespec spec; 955 spec.tv_sec = (now + delay) / 1000000000; 956 spec.tv_nsec = (now + delay) % 1000000000; 957 int err; 958 do { 959 err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &spec, NULL); 960 } while (err<0 && errno == EINTR); 961 } 962 963 checkExit(); 964 } 965 966 usleep(part.pause * ns2us(frameDuration)); 967 968 // For infinite parts, we've now played them at least once, so perhaps exit 969 if(exitPending() && !part.count) 970 break; 971 } 972 973 } 974 975 // Free textures created for looping parts now that the animation is done. 976 for (const Animation::Part& part : animation.parts) { 977 if (part.count != 1) { 978 const size_t fcount = part.frames.size(); 979 for (size_t j = 0; j < fcount; j++) { 980 const Animation::Frame& frame(part.frames[j]); 981 glDeleteTextures(1, &frame.tid); 982 } 983 } 984 } 985 986 return true; 987 } 988 989 void BootAnimation::releaseAnimation(Animation* animation) const 990 { 991 for (Vector<Animation::Part>::iterator it = animation->parts.begin(), 992 e = animation->parts.end(); it != e; ++it) { 993 if (it->animation) 994 releaseAnimation(it->animation); 995 } 996 if (animation->zip) 997 delete animation->zip; 998 delete animation; 999 } 1000 1001 BootAnimation::Animation* BootAnimation::loadAnimation(const String8& fn) 1002 { 1003 if (mLoadedFiles.indexOf(fn) >= 0) { 1004 ALOGE("File \"%s\" is already loaded. Cyclic ref is not allowed", 1005 fn.string()); 1006 return NULL; 1007 } 1008 ZipFileRO *zip = ZipFileRO::open(fn); 1009 if (zip == NULL) { 1010 ALOGE("Failed to open animation zip \"%s\": %s", 1011 fn.string(), strerror(errno)); 1012 return NULL; 1013 } 1014 1015 Animation *animation = new Animation; 1016 animation->fileName = fn; 1017 animation->zip = zip; 1018 animation->clockFont.map = nullptr; 1019 mLoadedFiles.add(animation->fileName); 1020 1021 parseAnimationDesc(*animation); 1022 if (!preloadZip(*animation)) { 1023 return NULL; 1024 } 1025 1026 1027 mLoadedFiles.remove(fn); 1028 return animation; 1029 } 1030 1031 bool BootAnimation::updateIsTimeAccurate() { 1032 static constexpr long long MAX_TIME_IN_PAST = 60000LL * 60LL * 24LL * 30LL; // 30 days 1033 static constexpr long long MAX_TIME_IN_FUTURE = 60000LL * 90LL; // 90 minutes 1034 1035 if (mTimeIsAccurate) { 1036 return true; 1037 } 1038 if (mShuttingDown) return true; 1039 struct stat statResult; 1040 1041 if(stat(TIME_FORMAT_12_HOUR_FLAG_FILE_PATH, &statResult) == 0) { 1042 mTimeFormat12Hour = true; 1043 } 1044 1045 if(stat(ACCURATE_TIME_FLAG_FILE_PATH, &statResult) == 0) { 1046 mTimeIsAccurate = true; 1047 return true; 1048 } 1049 1050 FILE* file = fopen(LAST_TIME_CHANGED_FILE_PATH, "r"); 1051 if (file != NULL) { 1052 long long lastChangedTime = 0; 1053 fscanf(file, "%lld", &lastChangedTime); 1054 fclose(file); 1055 if (lastChangedTime > 0) { 1056 struct timespec now; 1057 clock_gettime(CLOCK_REALTIME, &now); 1058 // Match the Java timestamp format 1059 long long rtcNow = (now.tv_sec * 1000LL) + (now.tv_nsec / 1000000LL); 1060 if (ACCURATE_TIME_EPOCH < rtcNow 1061 && lastChangedTime > (rtcNow - MAX_TIME_IN_PAST) 1062 && lastChangedTime < (rtcNow + MAX_TIME_IN_FUTURE)) { 1063 mTimeIsAccurate = true; 1064 } 1065 } 1066 } 1067 1068 return mTimeIsAccurate; 1069 } 1070 1071 BootAnimation::TimeCheckThread::TimeCheckThread(BootAnimation* bootAnimation) : Thread(false), 1072 mInotifyFd(-1), mSystemWd(-1), mTimeWd(-1), mBootAnimation(bootAnimation) {} 1073 1074 BootAnimation::TimeCheckThread::~TimeCheckThread() { 1075 // mInotifyFd may be -1 but that's ok since we're not at risk of attempting to close a valid FD. 1076 close(mInotifyFd); 1077 } 1078 1079 bool BootAnimation::TimeCheckThread::threadLoop() { 1080 bool shouldLoop = doThreadLoop() && !mBootAnimation->mTimeIsAccurate 1081 && mBootAnimation->mClockEnabled; 1082 if (!shouldLoop) { 1083 close(mInotifyFd); 1084 mInotifyFd = -1; 1085 } 1086 return shouldLoop; 1087 } 1088 1089 bool BootAnimation::TimeCheckThread::doThreadLoop() { 1090 static constexpr int BUFF_LEN (10 * (sizeof(struct inotify_event) + NAME_MAX + 1)); 1091 1092 // Poll instead of doing a blocking read so the Thread can exit if requested. 1093 struct pollfd pfd = { mInotifyFd, POLLIN, 0 }; 1094 ssize_t pollResult = poll(&pfd, 1, 1000); 1095 1096 if (pollResult == 0) { 1097 return true; 1098 } else if (pollResult < 0) { 1099 ALOGE("Could not poll inotify events"); 1100 return false; 1101 } 1102 1103 char buff[BUFF_LEN] __attribute__ ((aligned(__alignof__(struct inotify_event))));; 1104 ssize_t length = read(mInotifyFd, buff, BUFF_LEN); 1105 if (length == 0) { 1106 return true; 1107 } else if (length < 0) { 1108 ALOGE("Could not read inotify events"); 1109 return false; 1110 } 1111 1112 const struct inotify_event *event; 1113 for (char* ptr = buff; ptr < buff + length; ptr += sizeof(struct inotify_event) + event->len) { 1114 event = (const struct inotify_event *) ptr; 1115 if (event->wd == mSystemWd && strcmp(SYSTEM_TIME_DIR_NAME, event->name) == 0) { 1116 addTimeDirWatch(); 1117 } else if (event->wd == mTimeWd && (strcmp(LAST_TIME_CHANGED_FILE_NAME, event->name) == 0 1118 || strcmp(ACCURATE_TIME_FLAG_FILE_NAME, event->name) == 0)) { 1119 return !mBootAnimation->updateIsTimeAccurate(); 1120 } 1121 } 1122 1123 return true; 1124 } 1125 1126 void BootAnimation::TimeCheckThread::addTimeDirWatch() { 1127 mTimeWd = inotify_add_watch(mInotifyFd, SYSTEM_TIME_DIR_PATH, 1128 IN_CLOSE_WRITE | IN_MOVED_TO | IN_ATTRIB); 1129 if (mTimeWd > 0) { 1130 // No need to watch for the time directory to be created if it already exists 1131 inotify_rm_watch(mInotifyFd, mSystemWd); 1132 mSystemWd = -1; 1133 } 1134 } 1135 1136 status_t BootAnimation::TimeCheckThread::readyToRun() { 1137 mInotifyFd = inotify_init(); 1138 if (mInotifyFd < 0) { 1139 ALOGE("Could not initialize inotify fd"); 1140 return NO_INIT; 1141 } 1142 1143 mSystemWd = inotify_add_watch(mInotifyFd, SYSTEM_DATA_DIR_PATH, IN_CREATE | IN_ATTRIB); 1144 if (mSystemWd < 0) { 1145 close(mInotifyFd); 1146 mInotifyFd = -1; 1147 ALOGE("Could not add watch for %s", SYSTEM_DATA_DIR_PATH); 1148 return NO_INIT; 1149 } 1150 1151 addTimeDirWatch(); 1152 1153 if (mBootAnimation->updateIsTimeAccurate()) { 1154 close(mInotifyFd); 1155 mInotifyFd = -1; 1156 return ALREADY_EXISTS; 1157 } 1158 1159 return NO_ERROR; 1160 } 1161 1162 // --------------------------------------------------------------------------- 1163 1164 } 1165 ; // namespace android 1166