2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 #define LOG_TAG "BootAnimation"
21 #include <sys/inotify.h>
24 #include <sys/types.h>
27 #include <utils/misc.h>
31 #include <cutils/properties.h>
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>
39 #include <ui/PixelFormat.h>
41 #include <ui/Region.h>
42 #include <ui/DisplayInfo.h>
44 #include <gui/ISurfaceComposer.h>
45 #include <gui/Surface.h>
46 #include <gui/SurfaceComposerClient.h>
49 #pragma GCC diagnostic push
50 #pragma GCC diagnostic ignored "-Wunused-parameter"
53 #include <SkImageDecoder.h>
54 #pragma GCC diagnostic pop
57 #include <GLES/glext.h>
58 #include <EGL/eglext.h>
60 #include "BootAnimation.h"
61 #include "AudioPlayer.h"
65 static const char OEM_BOOTANIMATION_FILE[] = "/oem/media/bootanimation.zip";
66 static const char SYSTEM_BOOTANIMATION_FILE[] = "/system/media/bootanimation.zip";
67 static const char SYSTEM_ENCRYPTED_BOOTANIMATION_FILE[] = "/system/media/bootanimation-encrypted.zip";
68 static const char SYSTEM_DATA_DIR_PATH[] = "/data/system";
69 static const char SYSTEM_TIME_DIR_NAME[] = "time";
70 static const char SYSTEM_TIME_DIR_PATH[] = "/data/system/time";
71 static const char LAST_TIME_CHANGED_FILE_NAME[] = "last_time_change";
72 static const char LAST_TIME_CHANGED_FILE_PATH[] = "/data/system/time/last_time_change";
73 static const char ACCURATE_TIME_FLAG_FILE_NAME[] = "time_is_accurate";
74 static const char ACCURATE_TIME_FLAG_FILE_PATH[] = "/data/system/time/time_is_accurate";
75 static const char EXIT_PROP_NAME[] = "service.bootanim.exit";
76 static const int ANIM_ENTRY_NAME_MAX = 256;
78 // ---------------------------------------------------------------------------
80 BootAnimation::BootAnimation() : Thread(false), mClockEnabled(true), mTimeIsAccurate(false),
81 mTimeCheckThread(NULL) {
82 mSession = new SurfaceComposerClient();
85 BootAnimation::~BootAnimation() {}
87 void BootAnimation::onFirstRef() {
88 status_t err = mSession->linkToComposerDeath(this);
89 ALOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err));
90 if (err == NO_ERROR) {
91 run("BootAnimation", PRIORITY_DISPLAY);
95 sp<SurfaceComposerClient> BootAnimation::session() const {
100 void BootAnimation::binderDied(const wp<IBinder>&)
102 // woah, surfaceflinger died!
103 ALOGD("SurfaceFlinger died, exiting...");
105 // calling requestExit() is not enough here because the Surface code
106 // might be blocked on a condition variable that will never be updated.
107 kill( getpid(), SIGKILL );
109 if (mAudioPlayer != NULL) {
110 mAudioPlayer->requestExit();
114 status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets,
116 Asset* asset = assets.open(name, Asset::ACCESS_BUFFER);
120 SkImageDecoder::DecodeMemory(asset->getBuffer(false), asset->getLength(),
121 &bitmap, kUnknown_SkColorType, SkImageDecoder::kDecodePixels_Mode);
125 // ensure we can call getPixels(). No need to call unlock, since the
126 // bitmap will go out of scope when we return from this method.
129 const int w = bitmap.width();
130 const int h = bitmap.height();
131 const void* p = bitmap.getPixels();
133 GLint crop[4] = { 0, h, w, -h };
137 glGenTextures(1, &texture->name);
138 glBindTexture(GL_TEXTURE_2D, texture->name);
140 switch (bitmap.colorType()) {
141 case kAlpha_8_SkColorType:
142 glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA,
143 GL_UNSIGNED_BYTE, p);
145 case kARGB_4444_SkColorType:
146 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA,
147 GL_UNSIGNED_SHORT_4_4_4_4, p);
149 case kN32_SkColorType:
150 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA,
151 GL_UNSIGNED_BYTE, p);
153 case kRGB_565_SkColorType:
154 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB,
155 GL_UNSIGNED_SHORT_5_6_5, p);
161 glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);
162 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
163 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
164 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
165 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
169 status_t BootAnimation::initTexture(const Animation::Frame& frame)
171 //StopWatch watch("blah");
174 SkMemoryStream stream(frame.map->getDataPtr(), frame.map->getDataLength());
175 SkImageDecoder* codec = SkImageDecoder::Factory(&stream);
177 codec->setDitherImage(false);
178 codec->decode(&stream, &bitmap,
180 SkImageDecoder::kDecodePixels_Mode);
184 // FileMap memory is never released until application exit.
185 // Release it now as the texture is already loaded and the memory used for
186 // the packed resource can be released.
189 // ensure we can call getPixels(). No need to call unlock, since the
190 // bitmap will go out of scope when we return from this method.
193 const int w = bitmap.width();
194 const int h = bitmap.height();
195 const void* p = bitmap.getPixels();
197 GLint crop[4] = { 0, h, w, -h };
198 int tw = 1 << (31 - __builtin_clz(w));
199 int th = 1 << (31 - __builtin_clz(h));
200 if (tw < w) tw <<= 1;
201 if (th < h) th <<= 1;
203 switch (bitmap.colorType()) {
204 case kN32_SkColorType:
205 if (tw != w || th != h) {
206 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tw, th, 0, GL_RGBA,
207 GL_UNSIGNED_BYTE, 0);
208 glTexSubImage2D(GL_TEXTURE_2D, 0,
209 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, p);
211 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tw, th, 0, GL_RGBA,
212 GL_UNSIGNED_BYTE, p);
216 case kRGB_565_SkColorType:
217 if (tw != w || th != h) {
218 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, tw, th, 0, GL_RGB,
219 GL_UNSIGNED_SHORT_5_6_5, 0);
220 glTexSubImage2D(GL_TEXTURE_2D, 0,
221 0, 0, w, h, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, p);
223 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, tw, th, 0, GL_RGB,
224 GL_UNSIGNED_SHORT_5_6_5, p);
231 glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);
236 status_t BootAnimation::readyToRun() {
237 mAssets.addDefaultAssets();
239 sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
240 ISurfaceComposer::eDisplayIdMain));
242 status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &dinfo);
246 // create the native surface
247 sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
248 dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);
250 SurfaceComposerClient::openGlobalTransaction();
251 control->setLayer(0x40000000);
252 SurfaceComposerClient::closeGlobalTransaction();
254 sp<Surface> s = control->getSurface();
256 // initialize opengl and egl
257 const EGLint attribs[] = {
270 EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
272 eglInitialize(display, 0, 0);
273 eglChooseConfig(display, attribs, &config, 1, &numConfigs);
274 surface = eglCreateWindowSurface(display, config, s.get(), NULL);
275 context = eglCreateContext(display, config, NULL, NULL);
276 eglQuerySurface(display, surface, EGL_WIDTH, &w);
277 eglQuerySurface(display, surface, EGL_HEIGHT, &h);
279 if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)
287 mFlingerSurfaceControl = control;
290 // If the device has encryption turned on or is in process
291 // of being encrypted we show the encrypted boot animation.
292 char decrypt[PROPERTY_VALUE_MAX];
293 property_get("vold.decrypt", decrypt, "");
295 bool encryptedAnimation = atoi(decrypt) != 0 || !strcmp("trigger_restart_min_framework", decrypt);
297 if (encryptedAnimation && (access(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE, R_OK) == 0)) {
298 mZipFileName = SYSTEM_ENCRYPTED_BOOTANIMATION_FILE;
300 else if (access(OEM_BOOTANIMATION_FILE, R_OK) == 0) {
301 mZipFileName = OEM_BOOTANIMATION_FILE;
303 else if (access(SYSTEM_BOOTANIMATION_FILE, R_OK) == 0) {
304 mZipFileName = SYSTEM_BOOTANIMATION_FILE;
309 bool BootAnimation::threadLoop()
312 // We have no bootanimation file, so we use the stock android logo
314 if (mZipFileName.isEmpty()) {
320 eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
321 eglDestroyContext(mDisplay, mContext);
322 eglDestroySurface(mDisplay, mSurface);
323 mFlingerSurface.clear();
324 mFlingerSurfaceControl.clear();
325 eglTerminate(mDisplay);
326 IPCThreadState::self()->stopProcess();
330 bool BootAnimation::android()
332 initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");
333 initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png");
336 glShadeModel(GL_FLAT);
337 glDisable(GL_DITHER);
338 glDisable(GL_SCISSOR_TEST);
339 glClearColor(0,0,0,1);
340 glClear(GL_COLOR_BUFFER_BIT);
341 eglSwapBuffers(mDisplay, mSurface);
343 glEnable(GL_TEXTURE_2D);
344 glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
346 const GLint xc = (mWidth - mAndroid[0].w) / 2;
347 const GLint yc = (mHeight - mAndroid[0].h) / 2;
348 const Rect updateRect(xc, yc, xc + mAndroid[0].w, yc + mAndroid[0].h);
350 glScissor(updateRect.left, mHeight - updateRect.bottom, updateRect.width(),
351 updateRect.height());
354 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
355 glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
357 const nsecs_t startTime = systemTime();
359 nsecs_t now = systemTime();
360 double time = now - startTime;
361 float t = 4.0f * float(time / us2ns(16667)) / mAndroid[1].w;
362 GLint offset = (1 - (t - floorf(t))) * mAndroid[1].w;
363 GLint x = xc - offset;
365 glDisable(GL_SCISSOR_TEST);
366 glClear(GL_COLOR_BUFFER_BIT);
368 glEnable(GL_SCISSOR_TEST);
370 glBindTexture(GL_TEXTURE_2D, mAndroid[1].name);
371 glDrawTexiOES(x, yc, 0, mAndroid[1].w, mAndroid[1].h);
372 glDrawTexiOES(x + mAndroid[1].w, yc, 0, mAndroid[1].w, mAndroid[1].h);
375 glBindTexture(GL_TEXTURE_2D, mAndroid[0].name);
376 glDrawTexiOES(xc, yc, 0, mAndroid[0].w, mAndroid[0].h);
378 EGLBoolean res = eglSwapBuffers(mDisplay, mSurface);
379 if (res == EGL_FALSE)
382 // 12fps: don't animate too fast to preserve CPU
383 const nsecs_t sleepTime = 83333 - ns2us(systemTime() - now);
388 } while (!exitPending());
390 glDeleteTextures(1, &mAndroid[0].name);
391 glDeleteTextures(1, &mAndroid[1].name);
396 void BootAnimation::checkExit() {
397 // Allow surface flinger to gracefully request shutdown
398 char value[PROPERTY_VALUE_MAX];
399 property_get(EXIT_PROP_NAME, value, "0");
400 int exitnow = atoi(value);
403 if (mAudioPlayer != NULL) {
404 mAudioPlayer->requestExit();
409 // Parse a color represented as an HTML-style 'RRGGBB' string: each pair of
410 // characters in str is a hex number in [0, 255], which are converted to
411 // floating point values in the range [0.0, 1.0] and placed in the
412 // corresponding elements of color.
414 // If the input string isn't valid, parseColor returns false and color is
416 static bool parseColor(const char str[7], float color[3]) {
418 for (int i = 0; i < 3; i++) {
420 for (int j = 0; j < 2; j++) {
422 char c = str[2*i + j];
423 if (c >= '0' && c <= '9') val += c - '0';
424 else if (c >= 'A' && c <= 'F') val += (c - 'A') + 10;
425 else if (c >= 'a' && c <= 'f') val += (c - 'a') + 10;
428 tmpColor[i] = static_cast<float>(val) / 255.0f;
430 memcpy(color, tmpColor, sizeof(tmpColor));
435 static bool readFile(ZipFileRO* zip, const char* name, String8& outString)
437 ZipEntryRO entry = zip->findEntryByName(name);
438 ALOGE_IF(!entry, "couldn't find %s", name);
443 FileMap* entryMap = zip->createEntryFileMap(entry);
444 zip->releaseEntry(entry);
445 ALOGE_IF(!entryMap, "entryMap is null");
450 outString.setTo((char const*)entryMap->getDataPtr(), entryMap->getDataLength());
455 // The time glyphs are stored in a single image of height 64 pixels. Each digit is 40 pixels wide,
456 // and the colon character is half that at 20 pixels. The glyph order is '0123456789:'.
457 // We render 24 hour time.
458 void BootAnimation::drawTime(const Texture& clockTex, const int yPos) {
459 static constexpr char TIME_FORMAT[] = "%H:%M";
460 static constexpr int TIME_LENGTH = sizeof(TIME_FORMAT);
462 static constexpr int DIGIT_HEIGHT = 64;
463 static constexpr int DIGIT_WIDTH = 40;
464 static constexpr int COLON_WIDTH = DIGIT_WIDTH / 2;
465 static constexpr int TIME_WIDTH = (DIGIT_WIDTH * 4) + COLON_WIDTH;
467 if (clockTex.h < DIGIT_HEIGHT || clockTex.w < (10 * DIGIT_WIDTH + COLON_WIDTH)) {
468 ALOGE("Clock texture is too small; abandoning boot animation clock");
469 mClockEnabled = false;
475 struct tm* timeInfo = localtime(&rawtime);
477 char timeBuff[TIME_LENGTH];
478 size_t length = strftime(timeBuff, TIME_LENGTH, TIME_FORMAT, timeInfo);
480 if (length != TIME_LENGTH - 1) {
481 ALOGE("Couldn't format time; abandoning boot animation clock");
482 mClockEnabled = false;
486 glEnable(GL_BLEND); // Allow us to draw on top of the animation
487 glBindTexture(GL_TEXTURE_2D, clockTex.name);
489 int xPos = (mWidth - TIME_WIDTH) / 2;
490 int cropRect[4] = { 0, DIGIT_HEIGHT, DIGIT_WIDTH, -DIGIT_HEIGHT };
492 for (int i = 0; i < TIME_LENGTH - 1; i++) {
493 char c = timeBuff[i];
494 int width = DIGIT_WIDTH;
495 int pos = c - '0'; // Position in the character list
496 if (pos < 0 || pos > 10) {
503 // Crop the texture to only the pixels in the current glyph
504 int left = pos * DIGIT_WIDTH;
507 glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, cropRect);
509 glDrawTexiOES(xPos, yPos, 0, width, DIGIT_HEIGHT);
514 glDisable(GL_BLEND); // Return to the animation's default behaviour
515 glBindTexture(GL_TEXTURE_2D, 0);
518 bool BootAnimation::parseAnimationDesc(Animation& animation)
522 if (!readFile(animation.zip, "desc.txt", desString)) {
525 char const* s = desString.string();
527 // Create and initialize an AudioPlayer if we have an audio_conf.txt file
529 if (readFile(animation.zip, "audio_conf.txt", audioConf)) {
530 mAudioPlayer = new AudioPlayer;
531 if (!mAudioPlayer->init(audioConf.string())) {
532 ALOGE("mAudioPlayer.init failed");
537 // Parse the description file
539 const char* endl = strstr(s, "\n");
540 if (endl == NULL) break;
541 String8 line(s, endl - s);
542 const char* l = line.string();
549 char path[ANIM_ENTRY_NAME_MAX];
550 char color[7] = "000000"; // default to black if unspecified
553 if (sscanf(l, "%d %d %d", &width, &height, &fps) == 3) {
554 // ALOGD("> w=%d, h=%d, fps=%d", width, height, fps);
555 animation.width = width;
556 animation.height = height;
558 } else if (sscanf(l, " %c %d %d %s #%6s %d",
559 &pathType, &count, &pause, path, color, &clockPosY) >= 4) {
560 // ALOGD("> type=%c, count=%d, pause=%d, path=%s, color=%s, clockPosY=%d", pathType, count, pause, path, color, clockPosY);
561 Animation::Part part;
562 part.playUntilComplete = pathType == 'c';
566 part.clockPosY = clockPosY;
567 part.audioFile = NULL;
568 part.animation = NULL;
569 if (!parseColor(color, part.backgroundColor)) {
570 ALOGE("> invalid color '#%s'", color);
571 part.backgroundColor[0] = 0.0f;
572 part.backgroundColor[1] = 0.0f;
573 part.backgroundColor[2] = 0.0f;
575 animation.parts.add(part);
577 else if (strcmp(l, "$SYSTEM") == 0) {
578 // ALOGD("> SYSTEM");
579 Animation::Part part;
580 part.playUntilComplete = false;
583 part.audioFile = NULL;
584 part.animation = loadAnimation(String8(SYSTEM_BOOTANIMATION_FILE));
585 if (part.animation != NULL)
586 animation.parts.add(part);
594 bool BootAnimation::preloadZip(Animation& animation)
596 // read all the data structures
597 const size_t pcount = animation.parts.size();
599 ZipFileRO* mZip = animation.zip;
600 if (!mZip->startIteration(&cookie)) {
605 char name[ANIM_ENTRY_NAME_MAX];
606 while ((entry = mZip->nextEntry(cookie)) != NULL) {
607 const int foundEntryName = mZip->getEntryFileName(entry, name, ANIM_ENTRY_NAME_MAX);
608 if (foundEntryName > ANIM_ENTRY_NAME_MAX || foundEntryName == -1) {
609 ALOGE("Error fetching entry file name");
613 const String8 entryName(name);
614 const String8 path(entryName.getPathDir());
615 const String8 leaf(entryName.getPathLeaf());
616 if (leaf.size() > 0) {
617 for (size_t j=0 ; j<pcount ; j++) {
618 if (path == animation.parts[j].path) {
620 // supports only stored png files
621 if (mZip->getEntryInfo(entry, &method, NULL, NULL, NULL, NULL, NULL)) {
622 if (method == ZipFileRO::kCompressStored) {
623 FileMap* map = mZip->createEntryFileMap(entry);
625 Animation::Part& part(animation.parts.editItemAt(j));
626 if (leaf == "audio.wav") {
627 // a part may have at most one audio file
628 part.audioFile = map;
630 Animation::Frame frame;
633 part.frames.add(frame);
643 mZip->endIteration(cookie);
648 bool BootAnimation::movie()
650 Animation* animation = loadAnimation(mZipFileName);
651 if (animation == NULL)
654 bool anyPartHasClock = false;
655 for (size_t i=0; i < animation->parts.size(); i++) {
656 if(animation->parts[i].clockPosY >= 0) {
657 anyPartHasClock = true;
661 if (!anyPartHasClock) {
662 mClockEnabled = false;
665 // Blend required to draw time on top of animation frames.
666 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
667 glShadeModel(GL_FLAT);
668 glDisable(GL_DITHER);
669 glDisable(GL_SCISSOR_TEST);
672 glBindTexture(GL_TEXTURE_2D, 0);
673 glEnable(GL_TEXTURE_2D);
674 glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
675 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
676 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
677 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
678 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
680 bool clockTextureInitialized = false;
682 clockTextureInitialized = (initTexture(&mClock, mAssets, "images/clock64.png") == NO_ERROR);
683 mClockEnabled = clockTextureInitialized;
686 if (mClockEnabled && !updateIsTimeAccurate()) {
687 mTimeCheckThread = new TimeCheckThread(this);
688 mTimeCheckThread->run("BootAnimation::TimeCheckThread", PRIORITY_NORMAL);
691 playAnimation(*animation);
693 if (mTimeCheckThread != NULL) {
694 mTimeCheckThread->requestExit();
695 mTimeCheckThread = NULL;
698 releaseAnimation(animation);
700 if (clockTextureInitialized) {
701 glDeleteTextures(1, &mClock.name);
707 bool BootAnimation::playAnimation(const Animation& animation)
709 const size_t pcount = animation.parts.size();
710 const int xc = (mWidth - animation.width) / 2;
711 const int yc = ((mHeight - animation.height) / 2);
712 nsecs_t frameDuration = s2ns(1) / animation.fps;
714 Region clearReg(Rect(mWidth, mHeight));
715 clearReg.subtractSelf(Rect(xc, yc, xc+animation.width, yc+animation.height));
717 for (size_t i=0 ; i<pcount ; i++) {
718 const Animation::Part& part(animation.parts[i]);
719 const size_t fcount = part.frames.size();
720 glBindTexture(GL_TEXTURE_2D, 0);
722 // Handle animation package
723 if (part.animation != NULL) {
724 playAnimation(*part.animation);
727 continue; //to next part
730 for (int r=0 ; !part.count || r<part.count ; r++) {
731 // Exit any non playuntil complete parts immediately
732 if(exitPending() && !part.playUntilComplete)
735 // only play audio file the first time we animate the part
736 if (r == 0 && mAudioPlayer != NULL && part.audioFile) {
737 mAudioPlayer->playFile(part.audioFile);
741 part.backgroundColor[0],
742 part.backgroundColor[1],
743 part.backgroundColor[2],
746 for (size_t j=0 ; j<fcount && (!exitPending() || part.playUntilComplete) ; j++) {
747 const Animation::Frame& frame(part.frames[j]);
748 nsecs_t lastFrame = systemTime();
751 glBindTexture(GL_TEXTURE_2D, frame.tid);
753 if (part.count != 1) {
754 glGenTextures(1, &frame.tid);
755 glBindTexture(GL_TEXTURE_2D, frame.tid);
756 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
757 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
762 if (!clearReg.isEmpty()) {
763 Region::const_iterator head(clearReg.begin());
764 Region::const_iterator tail(clearReg.end());
765 glEnable(GL_SCISSOR_TEST);
766 while (head != tail) {
767 const Rect& r2(*head++);
768 glScissor(r2.left, mHeight - r2.bottom,
769 r2.width(), r2.height());
770 glClear(GL_COLOR_BUFFER_BIT);
772 glDisable(GL_SCISSOR_TEST);
774 // specify the y center as ceiling((mHeight - animation.height) / 2)
775 // which is equivalent to mHeight - (yc + animation.height)
776 glDrawTexiOES(xc, mHeight - (yc + animation.height),
777 0, animation.width, animation.height);
778 if (mClockEnabled && mTimeIsAccurate && part.clockPosY >= 0) {
779 drawTime(mClock, part.clockPosY);
782 eglSwapBuffers(mDisplay, mSurface);
784 nsecs_t now = systemTime();
785 nsecs_t delay = frameDuration - (now - lastFrame);
786 //ALOGD("%lld, %lld", ns2ms(now - lastFrame), ns2ms(delay));
790 struct timespec spec;
791 spec.tv_sec = (now + delay) / 1000000000;
792 spec.tv_nsec = (now + delay) % 1000000000;
795 err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &spec, NULL);
796 } while (err<0 && errno == EINTR);
802 usleep(part.pause * ns2us(frameDuration));
804 // For infinite parts, we've now played them at least once, so perhaps exit
805 if(exitPending() && !part.count)
809 // free the textures for this part
810 if (part.count != 1) {
811 for (size_t j=0 ; j<fcount ; j++) {
812 const Animation::Frame& frame(part.frames[j]);
813 glDeleteTextures(1, &frame.tid);
820 void BootAnimation::releaseAnimation(Animation* animation) const
822 for (Vector<Animation::Part>::iterator it = animation->parts.begin(),
823 e = animation->parts.end(); it != e; ++it) {
825 releaseAnimation(it->animation);
828 delete animation->zip;
832 BootAnimation::Animation* BootAnimation::loadAnimation(const String8& fn)
834 if (mLoadedFiles.indexOf(fn) >= 0) {
835 ALOGE("File \"%s\" is already loaded. Cyclic ref is not allowed",
839 ZipFileRO *zip = ZipFileRO::open(fn);
841 ALOGE("Failed to open animation zip \"%s\": %s",
842 fn.string(), strerror(errno));
846 Animation *animation = new Animation;
847 animation->fileName = fn;
848 animation->zip = zip;
849 mLoadedFiles.add(animation->fileName);
851 parseAnimationDesc(*animation);
852 preloadZip(*animation);
854 mLoadedFiles.remove(fn);
858 bool BootAnimation::updateIsTimeAccurate() {
859 static constexpr long long MAX_TIME_IN_PAST = 60000LL * 60LL * 24LL * 30LL; // 30 days
860 static constexpr long long MAX_TIME_IN_FUTURE = 60000LL * 90LL; // 90 minutes
862 if (mTimeIsAccurate) {
866 struct stat statResult;
867 if(stat(ACCURATE_TIME_FLAG_FILE_PATH, &statResult) == 0) {
868 mTimeIsAccurate = true;
872 FILE* file = fopen(LAST_TIME_CHANGED_FILE_PATH, "r");
874 long long lastChangedTime = 0;
875 fscanf(file, "%lld", &lastChangedTime);
877 if (lastChangedTime > 0) {
879 clock_gettime(CLOCK_REALTIME, &now);
880 // Match the Java timestamp format
881 long long rtcNow = (now.tv_sec * 1000LL) + (now.tv_nsec / 1000000LL);
882 if (lastChangedTime > rtcNow - MAX_TIME_IN_PAST
883 && lastChangedTime < rtcNow + MAX_TIME_IN_FUTURE) {
884 mTimeIsAccurate = true;
889 return mTimeIsAccurate;
892 BootAnimation::TimeCheckThread::TimeCheckThread(BootAnimation* bootAnimation) : Thread(false),
893 mInotifyFd(-1), mSystemWd(-1), mTimeWd(-1), mBootAnimation(bootAnimation) {}
895 BootAnimation::TimeCheckThread::~TimeCheckThread() {
896 // mInotifyFd may be -1 but that's ok since we're not at risk of attempting to close a valid FD.
900 bool BootAnimation::TimeCheckThread::threadLoop() {
901 bool shouldLoop = doThreadLoop() && !mBootAnimation->mTimeIsAccurate
902 && mBootAnimation->mClockEnabled;
910 bool BootAnimation::TimeCheckThread::doThreadLoop() {
911 static constexpr int BUFF_LEN (10 * (sizeof(struct inotify_event) + NAME_MAX + 1));
913 // Poll instead of doing a blocking read so the Thread can exit if requested.
914 struct pollfd pfd = { mInotifyFd, POLLIN, 0 };
915 ssize_t pollResult = poll(&pfd, 1, 1000);
917 if (pollResult == 0) {
919 } else if (pollResult < 0) {
920 ALOGE("Could not poll inotify events");
924 char buff[BUFF_LEN] __attribute__ ((aligned(__alignof__(struct inotify_event))));;
925 ssize_t length = read(mInotifyFd, buff, BUFF_LEN);
928 } else if (length < 0) {
929 ALOGE("Could not read inotify events");
933 const struct inotify_event *event;
934 for (char* ptr = buff; ptr < buff + length; ptr += sizeof(struct inotify_event) + event->len) {
935 event = (const struct inotify_event *) ptr;
936 if (event->wd == mSystemWd && strcmp(SYSTEM_TIME_DIR_NAME, event->name) == 0) {
938 } else if (event->wd == mTimeWd && (strcmp(LAST_TIME_CHANGED_FILE_NAME, event->name) == 0
939 || strcmp(ACCURATE_TIME_FLAG_FILE_NAME, event->name) == 0)) {
940 return !mBootAnimation->updateIsTimeAccurate();
947 void BootAnimation::TimeCheckThread::addTimeDirWatch() {
948 mTimeWd = inotify_add_watch(mInotifyFd, SYSTEM_TIME_DIR_PATH,
949 IN_CLOSE_WRITE | IN_MOVED_TO | IN_ATTRIB);
951 // No need to watch for the time directory to be created if it already exists
952 inotify_rm_watch(mInotifyFd, mSystemWd);
957 status_t BootAnimation::TimeCheckThread::readyToRun() {
958 mInotifyFd = inotify_init();
959 if (mInotifyFd < 0) {
960 ALOGE("Could not initialize inotify fd");
964 mSystemWd = inotify_add_watch(mInotifyFd, SYSTEM_DATA_DIR_PATH, IN_CREATE | IN_ATTRIB);
968 ALOGE("Could not add watch for %s", SYSTEM_DATA_DIR_PATH);
974 if (mBootAnimation->updateIsTimeAccurate()) {
977 return ALREADY_EXISTS;
983 // ---------------------------------------------------------------------------
986 ; // namespace android