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