diff options
| author | 2016-12-06 15:17:16 -0800 | |
|---|---|---|
| committer | 2016-12-06 15:17:16 -0800 | |
| commit | 82834baa358f55acb542e17da828b2d497cf8332 (patch) | |
| tree | 35a57c84d9810438a92cb7036cab3bba96580a5b /cmds/bootanimation/BootAnimation.cpp | |
| parent | 800a4e753e991ea23b4c72568b3150b8c232aa6a (diff) | |
| parent | 5b9b0db9f8532aea7c907d3f5ac8b7808b46c1c7 (diff) | |
Merge remote-tracking branch 'goog/stage-aosp-master' into HEAD
Diffstat (limited to 'cmds/bootanimation/BootAnimation.cpp')
| -rw-r--r-- | cmds/bootanimation/BootAnimation.cpp | 586 |
1 files changed, 470 insertions, 116 deletions
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 25b264ecff10..c9211939ec6c 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -18,6 +18,9 @@ #define LOG_TAG "BootAnimation" #include <stdint.h> +#include <sys/inotify.h> +#include <sys/poll.h> +#include <sys/stat.h> #include <sys/types.h> #include <math.h> #include <fcntl.h> @@ -55,26 +58,56 @@ #include <EGL/eglext.h> #include "BootAnimation.h" -#include "AudioPlayer.h" - -#define OEM_BOOTANIMATION_FILE "/oem/media/bootanimation.zip" -#define SYSTEM_BOOTANIMATION_FILE "/system/media/bootanimation.zip" -#define SYSTEM_ENCRYPTED_BOOTANIMATION_FILE "/system/media/bootanimation-encrypted.zip" -#define EXIT_PROP_NAME "service.bootanim.exit" +#include "audioplay.h" namespace android { +static const char OEM_BOOTANIMATION_FILE[] = "/oem/media/bootanimation.zip"; +static const char SYSTEM_BOOTANIMATION_FILE[] = "/system/media/bootanimation.zip"; +static const char SYSTEM_ENCRYPTED_BOOTANIMATION_FILE[] = "/system/media/bootanimation-encrypted.zip"; +static const char SYSTEM_DATA_DIR_PATH[] = "/data/system"; +static const char SYSTEM_TIME_DIR_NAME[] = "time"; +static const char SYSTEM_TIME_DIR_PATH[] = "/data/system/time"; +static const char CLOCK_FONT_ASSET[] = "images/clock_font.png"; +static const char CLOCK_FONT_ZIP_NAME[] = "clock_font.png"; +static const char LAST_TIME_CHANGED_FILE_NAME[] = "last_time_change"; +static const char LAST_TIME_CHANGED_FILE_PATH[] = "/data/system/time/last_time_change"; +static const char ACCURATE_TIME_FLAG_FILE_NAME[] = "time_is_accurate"; +static const char ACCURATE_TIME_FLAG_FILE_PATH[] = "/data/system/time/time_is_accurate"; +// Java timestamp format. Don't show the clock if the date is before 2000-01-01 00:00:00. +static const long long ACCURATE_TIME_EPOCH = 946684800000; +static constexpr char FONT_BEGIN_CHAR = ' '; +static constexpr char FONT_END_CHAR = '~' + 1; +static constexpr size_t FONT_NUM_CHARS = FONT_END_CHAR - FONT_BEGIN_CHAR + 1; +static constexpr size_t FONT_NUM_COLS = 16; +static constexpr size_t FONT_NUM_ROWS = FONT_NUM_CHARS / FONT_NUM_COLS; +static const int TEXT_CENTER_VALUE = INT_MAX; +static const int TEXT_MISSING_VALUE = INT_MIN; +static const char EXIT_PROP_NAME[] = "service.bootanim.exit"; +static const char PLAY_SOUND_PROP_NAME[] = "persist.sys.bootanim.play_sound"; static const int ANIM_ENTRY_NAME_MAX = 256; +static constexpr size_t TEXT_POS_LEN_MAX = 16; +static const char BOOT_COMPLETED_PROP_NAME[] = "sys.boot_completed"; +static const char BOOTREASON_PROP_NAME[] = "ro.boot.bootreason"; +// bootreasons list in "system/core/bootstat/bootstat.cpp". +static const std::vector<std::string> PLAY_SOUND_BOOTREASON_BLACKLIST { + "kernel_panic", + "Panic", + "Watchdog", +}; // --------------------------------------------------------------------------- -BootAnimation::BootAnimation() : Thread(false), mClockEnabled(true) { +BootAnimation::BootAnimation() : Thread(false), mClockEnabled(true), mTimeIsAccurate(false), + mTimeCheckThread(NULL) { mSession = new SurfaceComposerClient(); -} -BootAnimation::~BootAnimation() { + // If the system has already booted, the animation is not being used for a boot. + mSystemBoot = !property_get_bool(BOOT_COMPLETED_PROP_NAME, 0); } +BootAnimation::~BootAnimation() {} + void BootAnimation::onFirstRef() { status_t err = mSession->linkToComposerDeath(this); ALOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err)); @@ -97,9 +130,7 @@ void BootAnimation::binderDied(const wp<IBinder>&) // might be blocked on a condition variable that will never be updated. kill( getpid(), SIGKILL ); requestExit(); - if (mAudioPlayer != NULL) { - mAudioPlayer->requestExit(); - } + audioplay::destroy(); } status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets, @@ -154,15 +185,14 @@ status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets, glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + return NO_ERROR; } -status_t BootAnimation::initTexture(const Animation::Frame& frame) +status_t BootAnimation::initTexture(FileMap* map, int* width, int* height) { - //StopWatch watch("blah"); - SkBitmap bitmap; - SkMemoryStream stream(frame.map->getDataPtr(), frame.map->getDataLength()); + SkMemoryStream stream(map->getDataPtr(), map->getDataLength()); SkImageDecoder* codec = SkImageDecoder::Factory(&stream); if (codec != NULL) { codec->setDitherImage(false); @@ -175,7 +205,7 @@ status_t BootAnimation::initTexture(const Animation::Frame& frame) // FileMap memory is never released until application exit. // Release it now as the texture is already loaded and the memory used for // the packed resource can be released. - delete frame.map; + delete map; // ensure we can call getPixels(). No need to call unlock, since the // bitmap will go out of scope when we return from this method. @@ -193,25 +223,25 @@ status_t BootAnimation::initTexture(const Animation::Frame& frame) switch (bitmap.colorType()) { case kN32_SkColorType: - if (tw != w || th != h) { + if (!mUseNpotTextures && (tw != w || th != h)) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tw, th, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, p); } else { - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tw, th, 0, GL_RGBA, + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, p); } break; case kRGB_565_SkColorType: - if (tw != w || th != h) { + if (!mUseNpotTextures && (tw != w || th != h)) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, tw, th, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, p); } else { - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, tw, th, 0, GL_RGB, + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, p); } break; @@ -221,6 +251,9 @@ status_t BootAnimation::initTexture(const Animation::Frame& frame) glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop); + *width = w; + *height = h; + return NO_ERROR; } @@ -384,7 +417,6 @@ bool BootAnimation::android() return false; } - void BootAnimation::checkExit() { // Allow surface flinger to gracefully request shutdown char value[PROPERTY_VALUE_MAX]; @@ -392,10 +424,48 @@ void BootAnimation::checkExit() { int exitnow = atoi(value); if (exitnow) { requestExit(); - if (mAudioPlayer != NULL) { - mAudioPlayer->requestExit(); + } +} + +bool BootAnimation::validClock(const Animation::Part& part) { + return part.clockPosX != TEXT_MISSING_VALUE && part.clockPosY != TEXT_MISSING_VALUE; +} + +bool parseTextCoord(const char* str, int* dest) { + if (strcmp("c", str) == 0) { + *dest = TEXT_CENTER_VALUE; + return true; + } + + char* end; + int val = (int) strtol(str, &end, 0); + if (end == str || *end != '\0' || val == INT_MAX || val == INT_MIN) { + return false; + } + *dest = val; + return true; +} + +// Parse two position coordinates. If only string is non-empty, treat it as the y value. +void parsePosition(const char* str1, const char* str2, int* x, int* y) { + bool success = false; + if (strlen(str1) == 0) { // No values were specified + // success = false + } else if (strlen(str2) == 0) { // we have only one value + if (parseTextCoord(str1, y)) { + *x = TEXT_CENTER_VALUE; + success = true; + } + } else { + if (parseTextCoord(str1, x) && parseTextCoord(str2, y)) { + success = true; } } + + if (!success) { + *x = TEXT_MISSING_VALUE; + *y = TEXT_MISSING_VALUE; + } } // Parse a color represented as an HTML-style 'RRGGBB' string: each pair of @@ -444,69 +514,105 @@ static bool readFile(ZipFileRO* zip, const char* name, String8& outString) return true; } -// The time glyphs are stored in a single image of height 64 pixels. Each digit is 40 pixels wide, -// and the colon character is half that at 20 pixels. The glyph order is '0123456789:'. -// We render 24 hour time. -void BootAnimation::drawTime(const Texture& clockTex, const int yPos) { - static constexpr char TIME_FORMAT[] = "%H:%M"; - static constexpr int TIME_LENGTH = sizeof(TIME_FORMAT); +// The font image should be a 96x2 array of character images. The +// columns are the printable ASCII characters 0x20 - 0x7f. The +// top row is regular text; the bottom row is bold. +status_t BootAnimation::initFont(Font* font, const char* fallback) { + status_t status = NO_ERROR; - static constexpr int DIGIT_HEIGHT = 64; - static constexpr int DIGIT_WIDTH = 40; - static constexpr int COLON_WIDTH = DIGIT_WIDTH / 2; - static constexpr int TIME_WIDTH = (DIGIT_WIDTH * 4) + COLON_WIDTH; + if (font->map != nullptr) { + glGenTextures(1, &font->texture.name); + glBindTexture(GL_TEXTURE_2D, font->texture.name); - if (clockTex.h < DIGIT_HEIGHT || clockTex.w < (10 * DIGIT_WIDTH + COLON_WIDTH)) { - ALOGE("Clock texture is too small; abandoning boot animation clock"); - mClockEnabled = false; - return; + status = initTexture(font->map, &font->texture.w, &font->texture.h); + + glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + } else if (fallback != nullptr) { + status = initTexture(&font->texture, mAssets, fallback); + } else { + return NO_INIT; } - time_t rawtime; - time(&rawtime); - struct tm* timeInfo = localtime(&rawtime); + if (status == NO_ERROR) { + font->char_width = font->texture.w / FONT_NUM_COLS; + font->char_height = font->texture.h / FONT_NUM_ROWS / 2; // There are bold and regular rows + } - char timeBuff[TIME_LENGTH]; - size_t length = strftime(timeBuff, TIME_LENGTH, TIME_FORMAT, timeInfo); + return status; +} - if (length != TIME_LENGTH - 1) { - ALOGE("Couldn't format time; abandoning boot animation clock"); - mClockEnabled = false; - return; +void BootAnimation::drawText(const char* str, const Font& font, bool bold, int* x, int* y) { + glEnable(GL_BLEND); // Allow us to draw on top of the animation + glBindTexture(GL_TEXTURE_2D, font.texture.name); + + const int len = strlen(str); + const int strWidth = font.char_width * len; + + if (*x == TEXT_CENTER_VALUE) { + *x = (mWidth - strWidth) / 2; + } else if (*x < 0) { + *x = mWidth + *x - strWidth; + } + if (*y == TEXT_CENTER_VALUE) { + *y = (mHeight - font.char_height) / 2; + } else if (*y < 0) { + *y = mHeight + *y - font.char_height; } - glEnable(GL_BLEND); // Allow us to draw on top of the animation - glBindTexture(GL_TEXTURE_2D, clockTex.name); + int cropRect[4] = { 0, 0, font.char_width, -font.char_height }; - int xPos = (mWidth - TIME_WIDTH) / 2; - int cropRect[4] = { 0, DIGIT_HEIGHT, DIGIT_WIDTH, -DIGIT_HEIGHT }; + for (int i = 0; i < len; i++) { + char c = str[i]; - for (int i = 0; i < TIME_LENGTH - 1; i++) { - char c = timeBuff[i]; - int width = DIGIT_WIDTH; - int pos = c - '0'; // Position in the character list - if (pos < 0 || pos > 10) { - continue; - } - if (c == ':') { - width = COLON_WIDTH; + if (c < FONT_BEGIN_CHAR || c > FONT_END_CHAR) { + c = '?'; } // Crop the texture to only the pixels in the current glyph - int left = pos * DIGIT_WIDTH; - cropRect[0] = left; - cropRect[2] = width; + const int charPos = (c - FONT_BEGIN_CHAR); // Position in the list of valid characters + const int row = charPos / FONT_NUM_COLS; + const int col = charPos % FONT_NUM_COLS; + cropRect[0] = col * font.char_width; // Left of column + cropRect[1] = row * font.char_height * 2; // Top of row + // Move down to bottom of regular (one char_heigh) or bold (two char_heigh) line + cropRect[1] += bold ? 2 * font.char_height : font.char_height; glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, cropRect); - glDrawTexiOES(xPos, yPos, 0, width, DIGIT_HEIGHT); + glDrawTexiOES(*x, *y, 0, font.char_width, font.char_height); - xPos += width; + *x += font.char_width; } glDisable(GL_BLEND); // Return to the animation's default behaviour glBindTexture(GL_TEXTURE_2D, 0); } +// We render 24 hour time. +void BootAnimation::drawClock(const Font& font, const int xPos, const int yPos) { + static constexpr char TIME_FORMAT[] = "%H:%M"; + static constexpr int TIME_LENGTH = 6; + + time_t rawtime; + time(&rawtime); + struct tm* timeInfo = localtime(&rawtime); + + char timeBuff[TIME_LENGTH]; + size_t length = strftime(timeBuff, TIME_LENGTH, TIME_FORMAT, timeInfo); + + if (length != TIME_LENGTH - 1) { + ALOGE("Couldn't format time; abandoning boot animation clock"); + mClockEnabled = false; + return; + } + + int x = xPos; + int y = yPos; + drawText(timeBuff, font, false, &x, &y); +} + bool BootAnimation::parseAnimationDesc(Animation& animation) { String8 desString; @@ -516,16 +622,6 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) } char const* s = desString.string(); - // Create and initialize an AudioPlayer if we have an audio_conf.txt file - String8 audioConf; - if (readFile(animation.zip, "audio_conf.txt", audioConf)) { - mAudioPlayer = new AudioPlayer; - if (!mAudioPlayer->init(audioConf.string())) { - ALOGE("mAudioPlayer.init failed"); - mAudioPlayer = NULL; - } - } - // Parse the description file for (;;) { const char* endl = strstr(s, "\n"); @@ -537,9 +633,10 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) int height = 0; int count = 0; int pause = 0; - int clockPosY = -1; char path[ANIM_ENTRY_NAME_MAX]; char color[7] = "000000"; // default to black if unspecified + char clockPos1[TEXT_POS_LEN_MAX + 1] = ""; + char clockPos2[TEXT_POS_LEN_MAX + 1] = ""; char pathType; if (sscanf(l, "%d %d %d", &width, &height, &fps) == 3) { @@ -547,16 +644,16 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) animation.width = width; animation.height = height; animation.fps = fps; - } else if (sscanf(l, " %c %d %d %s #%6s %d", - &pathType, &count, &pause, path, color, &clockPosY) >= 4) { - // ALOGD("> type=%c, count=%d, pause=%d, path=%s, color=%s, clockPosY=%d", pathType, count, pause, path, color, clockPosY); + } else if (sscanf(l, " %c %d %d %s #%6s %16s %16s", + &pathType, &count, &pause, path, color, clockPos1, clockPos2) >= 4) { + //ALOGD("> type=%c, count=%d, pause=%d, path=%s, color=%s, clockPos1=%s, clockPos2=%s", + // pathType, count, pause, path, color, clockPos1, clockPos2); Animation::Part part; part.playUntilComplete = pathType == 'c'; part.count = count; part.pause = pause; part.path = path; - part.clockPosY = clockPosY; - part.audioFile = NULL; + part.audioData = NULL; part.animation = NULL; if (!parseColor(color, part.backgroundColor)) { ALOGE("> invalid color '#%s'", color); @@ -564,6 +661,7 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) part.backgroundColor[1] = 0.0f; part.backgroundColor[2] = 0.0f; } + parsePosition(clockPos1, clockPos2, &part.clockPosX, &part.clockPosY); animation.parts.add(part); } else if (strcmp(l, "$SYSTEM") == 0) { @@ -572,7 +670,7 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) part.playUntilComplete = false; part.count = 1; part.pause = 0; - part.audioFile = NULL; + part.audioData = NULL; part.animation = loadAnimation(String8(SYSTEM_BOOTANIMATION_FILE)); if (part.animation != NULL) animation.parts.add(part); @@ -588,15 +686,16 @@ bool BootAnimation::preloadZip(Animation& animation) // read all the data structures const size_t pcount = animation.parts.size(); void *cookie = NULL; - ZipFileRO* mZip = animation.zip; - if (!mZip->startIteration(&cookie)) { + ZipFileRO* zip = animation.zip; + if (!zip->startIteration(&cookie)) { return false; } + Animation::Part* partWithAudio = NULL; ZipEntryRO entry; char name[ANIM_ENTRY_NAME_MAX]; - while ((entry = mZip->nextEntry(cookie)) != NULL) { - const int foundEntryName = mZip->getEntryFileName(entry, name, ANIM_ENTRY_NAME_MAX); + while ((entry = zip->nextEntry(cookie)) != NULL) { + const int foundEntryName = zip->getEntryFileName(entry, name, ANIM_ENTRY_NAME_MAX); if (foundEntryName > ANIM_ENTRY_NAME_MAX || foundEntryName == -1) { ALOGE("Error fetching entry file name"); continue; @@ -606,25 +705,44 @@ bool BootAnimation::preloadZip(Animation& animation) const String8 path(entryName.getPathDir()); const String8 leaf(entryName.getPathLeaf()); if (leaf.size() > 0) { - for (size_t j=0 ; j<pcount ; j++) { + if (entryName == CLOCK_FONT_ZIP_NAME) { + FileMap* map = zip->createEntryFileMap(entry); + if (map) { + animation.clockFont.map = map; + } + continue; + } + + for (size_t j = 0; j < pcount; j++) { if (path == animation.parts[j].path) { uint16_t method; // supports only stored png files - if (mZip->getEntryInfo(entry, &method, NULL, NULL, NULL, NULL, NULL)) { + if (zip->getEntryInfo(entry, &method, NULL, NULL, NULL, NULL, NULL)) { if (method == ZipFileRO::kCompressStored) { - FileMap* map = mZip->createEntryFileMap(entry); + FileMap* map = zip->createEntryFileMap(entry); if (map) { Animation::Part& part(animation.parts.editItemAt(j)); if (leaf == "audio.wav") { // a part may have at most one audio file - part.audioFile = map; + part.audioData = (uint8_t *)map->getDataPtr(); + part.audioLength = map->getDataLength(); + partWithAudio = ∂ + } else if (leaf == "trim.txt") { + part.trimData.setTo((char const*)map->getDataPtr(), + map->getDataLength()); } else { Animation::Frame frame; frame.name = leaf; frame.map = map; + frame.trimWidth = animation.width; + frame.trimHeight = animation.height; + frame.trimX = 0; + frame.trimY = 0; part.frames.add(frame); } } + } else { + ALOGE("bootanimation.zip is compressed; must be only stored"); } } } @@ -632,18 +750,76 @@ bool BootAnimation::preloadZip(Animation& animation) } } - mZip->endIteration(cookie); + // If there is trimData present, override the positioning defaults. + for (Animation::Part& part : animation.parts) { + const char* trimDataStr = part.trimData.string(); + for (size_t frameIdx = 0; frameIdx < part.frames.size(); frameIdx++) { + const char* endl = strstr(trimDataStr, "\n"); + // No more trimData for this part. + if (endl == NULL) { + break; + } + String8 line(trimDataStr, endl - trimDataStr); + const char* lineStr = line.string(); + trimDataStr = ++endl; + int width = 0, height = 0, x = 0, y = 0; + if (sscanf(lineStr, "%dx%d+%d+%d", &width, &height, &x, &y) == 4) { + Animation::Frame& frame(part.frames.editItemAt(frameIdx)); + frame.trimWidth = width; + frame.trimHeight = height; + frame.trimX = x; + frame.trimY = y; + } else { + ALOGE("Error parsing trim.txt, line: %s", lineStr); + break; + } + } + } + + // Create and initialize audioplay if there is a wav file in any of the animations. + if (partWithAudio != NULL) { + ALOGD("found audio.wav, creating playback engine"); + if (!audioplay::create(partWithAudio->audioData, partWithAudio->audioLength)) { + return false; + } + } + + zip->endIteration(cookie); return true; } bool BootAnimation::movie() { - Animation* animation = loadAnimation(mZipFileName); if (animation == NULL) return false; + bool anyPartHasClock = false; + for (size_t i=0; i < animation->parts.size(); i++) { + if(validClock(animation->parts[i])) { + anyPartHasClock = true; + break; + } + } + if (!anyPartHasClock) { + mClockEnabled = false; + } + + // Check if npot textures are supported + mUseNpotTextures = false; + String8 gl_extensions; + const char* exts = reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)); + if (!exts) { + glGetError(); + } else { + gl_extensions.setTo(exts); + if ((gl_extensions.find("GL_ARB_texture_non_power_of_two") != -1) || + (gl_extensions.find("GL_OES_texture_npot") != -1)) { + mUseNpotTextures = true; + } + } + // Blend required to draw time on top of animation frames. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glShadeModel(GL_FLAT); @@ -659,17 +835,29 @@ bool BootAnimation::movie() glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - bool clockTextureInitialized = false; + bool clockFontInitialized = false; if (mClockEnabled) { - clockTextureInitialized = (initTexture(&mClock, mAssets, "images/clock64.png") == NO_ERROR); - mClockEnabled = clockTextureInitialized; + clockFontInitialized = + (initFont(&animation->clockFont, CLOCK_FONT_ASSET) == NO_ERROR); + mClockEnabled = clockFontInitialized; + } + + if (mClockEnabled && !updateIsTimeAccurate()) { + mTimeCheckThread = new TimeCheckThread(this); + mTimeCheckThread->run("BootAnimation::TimeCheckThread", PRIORITY_NORMAL); } playAnimation(*animation); + + if (mTimeCheckThread != NULL) { + mTimeCheckThread->requestExit(); + mTimeCheckThread = NULL; + } + releaseAnimation(animation); - if (clockTextureInitialized) { - glDeleteTextures(1, &mClock.name); + if (clockFontInitialized) { + glDeleteTextures(1, &animation->clockFont.texture.name); } return false; @@ -678,12 +866,9 @@ bool BootAnimation::movie() bool BootAnimation::playAnimation(const Animation& animation) { const size_t pcount = animation.parts.size(); - const int xc = (mWidth - animation.width) / 2; - const int yc = ((mHeight - animation.height) / 2); nsecs_t frameDuration = s2ns(1) / animation.fps; - - Region clearReg(Rect(mWidth, mHeight)); - clearReg.subtractSelf(Rect(xc, yc, xc+animation.width, yc+animation.height)); + const int animationX = (mWidth - animation.width) / 2; + const int animationY = (mHeight - animation.height) / 2; for (size_t i=0 ; i<pcount ; i++) { const Animation::Part& part(animation.parts[i]); @@ -704,8 +889,9 @@ bool BootAnimation::playAnimation(const Animation& animation) break; // only play audio file the first time we animate the part - if (r == 0 && mAudioPlayer != NULL && part.audioFile) { - mAudioPlayer->playFile(part.audioFile); + if (r == 0 && part.audioData && playSoundsAllowed()) { + ALOGD("playing clip for part%d, size=%d", (int) i, part.audioLength); + audioplay::playClip(part.audioData, part.audioLength); } glClearColor( @@ -727,27 +913,31 @@ bool BootAnimation::playAnimation(const Animation& animation) glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } - initTexture(frame); + int w, h; + initTexture(frame.map, &w, &h); } + const int xc = animationX + frame.trimX; + const int yc = animationY + frame.trimY; + Region clearReg(Rect(mWidth, mHeight)); + clearReg.subtractSelf(Rect(xc, yc, xc+frame.trimWidth, yc+frame.trimHeight)); if (!clearReg.isEmpty()) { Region::const_iterator head(clearReg.begin()); Region::const_iterator tail(clearReg.end()); glEnable(GL_SCISSOR_TEST); while (head != tail) { const Rect& r2(*head++); - glScissor(r2.left, mHeight - r2.bottom, - r2.width(), r2.height()); + glScissor(r2.left, mHeight - r2.bottom, r2.width(), r2.height()); glClear(GL_COLOR_BUFFER_BIT); } glDisable(GL_SCISSOR_TEST); } - // specify the y center as ceiling((mHeight - animation.height) / 2) - // which is equivalent to mHeight - (yc + animation.height) - glDrawTexiOES(xc, mHeight - (yc + animation.height), - 0, animation.width, animation.height); - if (mClockEnabled && part.clockPosY >= 0) { - drawTime(mClock, part.clockPosY); + // specify the y center as ceiling((mHeight - frame.trimHeight) / 2) + // which is equivalent to mHeight - (yc + frame.trimHeight) + glDrawTexiOES(xc, mHeight - (yc + frame.trimHeight), + 0, frame.trimWidth, frame.trimHeight); + if (mClockEnabled && mTimeIsAccurate && validClock(part)) { + drawClock(animation.clockFont, part.clockPosX, part.clockPosY); } eglSwapBuffers(mDisplay, mSurface); @@ -777,14 +967,23 @@ bool BootAnimation::playAnimation(const Animation& animation) break; } - // free the textures for this part + } + + // Free textures created for looping parts now that the animation is done. + for (const Animation::Part& part : animation.parts) { if (part.count != 1) { - for (size_t j=0 ; j<fcount ; j++) { + const size_t fcount = part.frames.size(); + for (size_t j = 0; j < fcount; j++) { const Animation::Frame& frame(part.frames[j]); glDeleteTextures(1, &frame.tid); } } } + + // we've finally played everything we're going to play + audioplay::setPlaying(false); + audioplay::destroy(); + return true; } @@ -817,14 +1016,169 @@ BootAnimation::Animation* BootAnimation::loadAnimation(const String8& fn) Animation *animation = new Animation; animation->fileName = fn; animation->zip = zip; + animation->clockFont.map = nullptr; mLoadedFiles.add(animation->fileName); parseAnimationDesc(*animation); - preloadZip(*animation); + if (!preloadZip(*animation)) { + return NULL; + } + mLoadedFiles.remove(fn); return animation; } + +bool BootAnimation::playSoundsAllowed() const { + // Only play sounds for system boots, not runtime restarts. + if (!mSystemBoot) { + return false; + } + + // Read the system property to see if we should play the sound. + // If it's not present, default to allowed. + if (!property_get_bool(PLAY_SOUND_PROP_NAME, 1)) { + return false; + } + + // Don't play sounds if this is a reboot due to an error. + char bootreason[PROPERTY_VALUE_MAX]; + if (property_get(BOOTREASON_PROP_NAME, bootreason, nullptr) > 0) { + for (const auto& str : PLAY_SOUND_BOOTREASON_BLACKLIST) { + if (strcasecmp(str.c_str(), bootreason) == 0) { + return false; + } + } + } + return true; +} + +bool BootAnimation::updateIsTimeAccurate() { + static constexpr long long MAX_TIME_IN_PAST = 60000LL * 60LL * 24LL * 30LL; // 30 days + static constexpr long long MAX_TIME_IN_FUTURE = 60000LL * 90LL; // 90 minutes + + if (mTimeIsAccurate) { + return true; + } + + struct stat statResult; + if(stat(ACCURATE_TIME_FLAG_FILE_PATH, &statResult) == 0) { + mTimeIsAccurate = true; + return true; + } + + FILE* file = fopen(LAST_TIME_CHANGED_FILE_PATH, "r"); + if (file != NULL) { + long long lastChangedTime = 0; + fscanf(file, "%lld", &lastChangedTime); + fclose(file); + if (lastChangedTime > 0) { + struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); + // Match the Java timestamp format + long long rtcNow = (now.tv_sec * 1000LL) + (now.tv_nsec / 1000000LL); + if (ACCURATE_TIME_EPOCH < rtcNow + && lastChangedTime > (rtcNow - MAX_TIME_IN_PAST) + && lastChangedTime < (rtcNow + MAX_TIME_IN_FUTURE)) { + mTimeIsAccurate = true; + } + } + } + + return mTimeIsAccurate; +} + +BootAnimation::TimeCheckThread::TimeCheckThread(BootAnimation* bootAnimation) : Thread(false), + mInotifyFd(-1), mSystemWd(-1), mTimeWd(-1), mBootAnimation(bootAnimation) {} + +BootAnimation::TimeCheckThread::~TimeCheckThread() { + // mInotifyFd may be -1 but that's ok since we're not at risk of attempting to close a valid FD. + close(mInotifyFd); +} + +bool BootAnimation::TimeCheckThread::threadLoop() { + bool shouldLoop = doThreadLoop() && !mBootAnimation->mTimeIsAccurate + && mBootAnimation->mClockEnabled; + if (!shouldLoop) { + close(mInotifyFd); + mInotifyFd = -1; + } + return shouldLoop; +} + +bool BootAnimation::TimeCheckThread::doThreadLoop() { + static constexpr int BUFF_LEN (10 * (sizeof(struct inotify_event) + NAME_MAX + 1)); + + // Poll instead of doing a blocking read so the Thread can exit if requested. + struct pollfd pfd = { mInotifyFd, POLLIN, 0 }; + ssize_t pollResult = poll(&pfd, 1, 1000); + + if (pollResult == 0) { + return true; + } else if (pollResult < 0) { + ALOGE("Could not poll inotify events"); + return false; + } + + char buff[BUFF_LEN] __attribute__ ((aligned(__alignof__(struct inotify_event))));; + ssize_t length = read(mInotifyFd, buff, BUFF_LEN); + if (length == 0) { + return true; + } else if (length < 0) { + ALOGE("Could not read inotify events"); + return false; + } + + const struct inotify_event *event; + for (char* ptr = buff; ptr < buff + length; ptr += sizeof(struct inotify_event) + event->len) { + event = (const struct inotify_event *) ptr; + if (event->wd == mSystemWd && strcmp(SYSTEM_TIME_DIR_NAME, event->name) == 0) { + addTimeDirWatch(); + } else if (event->wd == mTimeWd && (strcmp(LAST_TIME_CHANGED_FILE_NAME, event->name) == 0 + || strcmp(ACCURATE_TIME_FLAG_FILE_NAME, event->name) == 0)) { + return !mBootAnimation->updateIsTimeAccurate(); + } + } + + return true; +} + +void BootAnimation::TimeCheckThread::addTimeDirWatch() { + mTimeWd = inotify_add_watch(mInotifyFd, SYSTEM_TIME_DIR_PATH, + IN_CLOSE_WRITE | IN_MOVED_TO | IN_ATTRIB); + if (mTimeWd > 0) { + // No need to watch for the time directory to be created if it already exists + inotify_rm_watch(mInotifyFd, mSystemWd); + mSystemWd = -1; + } +} + +status_t BootAnimation::TimeCheckThread::readyToRun() { + mInotifyFd = inotify_init(); + if (mInotifyFd < 0) { + ALOGE("Could not initialize inotify fd"); + return NO_INIT; + } + + mSystemWd = inotify_add_watch(mInotifyFd, SYSTEM_DATA_DIR_PATH, IN_CREATE | IN_ATTRIB); + if (mSystemWd < 0) { + close(mInotifyFd); + mInotifyFd = -1; + ALOGE("Could not add watch for %s", SYSTEM_DATA_DIR_PATH); + return NO_INIT; + } + + addTimeDirWatch(); + + if (mBootAnimation->updateIsTimeAccurate()) { + close(mInotifyFd); + mInotifyFd = -1; + return ALREADY_EXISTS; + } + + return NO_ERROR; +} + // --------------------------------------------------------------------------- } |