summaryrefslogtreecommitdiff
path: root/cmds/bootanimation/BootAnimation.cpp
diff options
context:
space:
mode:
author Bill Yi <byi@google.com> 2016-12-06 15:17:16 -0800
committer Bill Yi <byi@google.com> 2016-12-06 15:17:16 -0800
commit82834baa358f55acb542e17da828b2d497cf8332 (patch)
tree35a57c84d9810438a92cb7036cab3bba96580a5b /cmds/bootanimation/BootAnimation.cpp
parent800a4e753e991ea23b4c72568b3150b8c232aa6a (diff)
parent5b9b0db9f8532aea7c907d3f5ac8b7808b46c1c7 (diff)
Merge remote-tracking branch 'goog/stage-aosp-master' into HEAD
Diffstat (limited to 'cmds/bootanimation/BootAnimation.cpp')
-rw-r--r--cmds/bootanimation/BootAnimation.cpp586
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 = &part;
+ } 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;
+}
+
// ---------------------------------------------------------------------------
}