getEntryInfo crashes on 64-bit devices because "long" types were being passed int pointers (that pointed to a stack frame) that were reinterpret_cast'ed to long* (sigh.). To fix this issue once and for all, use types with explicitly defined widths. This change also removes some dead invariant checking from Asset.cpp instead of cleaning it up. Note that we've introduced a wart in NativeLibraryHelper, where we need to deal with zlib's uLong type, which is "at least 32 bits wide". bug: 21622286 Change-Id: Iae675a9601db7aae03a8b80b40321d2cc1d97f50
680 lines
22 KiB
C++
680 lines
22 KiB
C++
/*
|
|
* Copyright (C) 2007 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#define LOG_NDEBUG 0
|
|
#define LOG_TAG "BootAnimation"
|
|
|
|
#include <stdint.h>
|
|
#include <sys/types.h>
|
|
#include <math.h>
|
|
#include <fcntl.h>
|
|
#include <utils/misc.h>
|
|
#include <signal.h>
|
|
#include <time.h>
|
|
|
|
#include <cutils/properties.h>
|
|
|
|
#include <androidfw/AssetManager.h>
|
|
#include <binder/IPCThreadState.h>
|
|
#include <utils/Atomic.h>
|
|
#include <utils/Errors.h>
|
|
#include <utils/Log.h>
|
|
|
|
#include <ui/PixelFormat.h>
|
|
#include <ui/Rect.h>
|
|
#include <ui/Region.h>
|
|
#include <ui/DisplayInfo.h>
|
|
|
|
#include <gui/ISurfaceComposer.h>
|
|
#include <gui/Surface.h>
|
|
#include <gui/SurfaceComposerClient.h>
|
|
|
|
// TODO: Fix Skia.
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
|
#include <SkBitmap.h>
|
|
#include <SkStream.h>
|
|
#include <SkImageDecoder.h>
|
|
#pragma GCC diagnostic pop
|
|
|
|
#include <GLES/gl.h>
|
|
#include <GLES/glext.h>
|
|
#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"
|
|
|
|
namespace android {
|
|
|
|
static const int ANIM_ENTRY_NAME_MAX = 256;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
BootAnimation::BootAnimation() : Thread(false), mZip(NULL)
|
|
{
|
|
mSession = new SurfaceComposerClient();
|
|
}
|
|
|
|
BootAnimation::~BootAnimation() {
|
|
if (mZip != NULL) {
|
|
delete mZip;
|
|
}
|
|
}
|
|
|
|
void BootAnimation::onFirstRef() {
|
|
status_t err = mSession->linkToComposerDeath(this);
|
|
ALOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err));
|
|
if (err == NO_ERROR) {
|
|
run("BootAnimation", PRIORITY_DISPLAY);
|
|
}
|
|
}
|
|
|
|
sp<SurfaceComposerClient> BootAnimation::session() const {
|
|
return mSession;
|
|
}
|
|
|
|
|
|
void BootAnimation::binderDied(const wp<IBinder>&)
|
|
{
|
|
// woah, surfaceflinger died!
|
|
ALOGD("SurfaceFlinger died, exiting...");
|
|
|
|
// calling requestExit() is not enough here because the Surface code
|
|
// might be blocked on a condition variable that will never be updated.
|
|
kill( getpid(), SIGKILL );
|
|
requestExit();
|
|
if (mAudioPlayer != NULL) {
|
|
mAudioPlayer->requestExit();
|
|
}
|
|
}
|
|
|
|
status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets,
|
|
const char* name) {
|
|
Asset* asset = assets.open(name, Asset::ACCESS_BUFFER);
|
|
if (asset == NULL)
|
|
return NO_INIT;
|
|
SkBitmap bitmap;
|
|
SkImageDecoder::DecodeMemory(asset->getBuffer(false), asset->getLength(),
|
|
&bitmap, kUnknown_SkColorType, SkImageDecoder::kDecodePixels_Mode);
|
|
asset->close();
|
|
delete asset;
|
|
|
|
// ensure we can call getPixels(). No need to call unlock, since the
|
|
// bitmap will go out of scope when we return from this method.
|
|
bitmap.lockPixels();
|
|
|
|
const int w = bitmap.width();
|
|
const int h = bitmap.height();
|
|
const void* p = bitmap.getPixels();
|
|
|
|
GLint crop[4] = { 0, h, w, -h };
|
|
texture->w = w;
|
|
texture->h = h;
|
|
|
|
glGenTextures(1, &texture->name);
|
|
glBindTexture(GL_TEXTURE_2D, texture->name);
|
|
|
|
switch (bitmap.colorType()) {
|
|
case kAlpha_8_SkColorType:
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA,
|
|
GL_UNSIGNED_BYTE, p);
|
|
break;
|
|
case kARGB_4444_SkColorType:
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA,
|
|
GL_UNSIGNED_SHORT_4_4_4_4, p);
|
|
break;
|
|
case kN32_SkColorType:
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA,
|
|
GL_UNSIGNED_BYTE, p);
|
|
break;
|
|
case kRGB_565_SkColorType:
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB,
|
|
GL_UNSIGNED_SHORT_5_6_5, p);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);
|
|
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);
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t BootAnimation::initTexture(const Animation::Frame& frame)
|
|
{
|
|
//StopWatch watch("blah");
|
|
|
|
SkBitmap bitmap;
|
|
SkMemoryStream stream(frame.map->getDataPtr(), frame.map->getDataLength());
|
|
SkImageDecoder* codec = SkImageDecoder::Factory(&stream);
|
|
if (codec != NULL) {
|
|
codec->setDitherImage(false);
|
|
codec->decode(&stream, &bitmap,
|
|
kN32_SkColorType,
|
|
SkImageDecoder::kDecodePixels_Mode);
|
|
delete codec;
|
|
}
|
|
|
|
// 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;
|
|
|
|
// ensure we can call getPixels(). No need to call unlock, since the
|
|
// bitmap will go out of scope when we return from this method.
|
|
bitmap.lockPixels();
|
|
|
|
const int w = bitmap.width();
|
|
const int h = bitmap.height();
|
|
const void* p = bitmap.getPixels();
|
|
|
|
GLint crop[4] = { 0, h, w, -h };
|
|
int tw = 1 << (31 - __builtin_clz(w));
|
|
int th = 1 << (31 - __builtin_clz(h));
|
|
if (tw < w) tw <<= 1;
|
|
if (th < h) th <<= 1;
|
|
|
|
switch (bitmap.colorType()) {
|
|
case kN32_SkColorType:
|
|
if (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,
|
|
GL_UNSIGNED_BYTE, p);
|
|
}
|
|
break;
|
|
|
|
case kRGB_565_SkColorType:
|
|
if (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,
|
|
GL_UNSIGNED_SHORT_5_6_5, p);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t BootAnimation::readyToRun() {
|
|
mAssets.addDefaultAssets();
|
|
|
|
sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
|
|
ISurfaceComposer::eDisplayIdMain));
|
|
DisplayInfo dinfo;
|
|
status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &dinfo);
|
|
if (status)
|
|
return -1;
|
|
|
|
// create the native surface
|
|
sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
|
|
dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);
|
|
|
|
SurfaceComposerClient::openGlobalTransaction();
|
|
control->setLayer(0x40000000);
|
|
SurfaceComposerClient::closeGlobalTransaction();
|
|
|
|
sp<Surface> s = control->getSurface();
|
|
|
|
// initialize opengl and egl
|
|
const EGLint attribs[] = {
|
|
EGL_RED_SIZE, 8,
|
|
EGL_GREEN_SIZE, 8,
|
|
EGL_BLUE_SIZE, 8,
|
|
EGL_DEPTH_SIZE, 0,
|
|
EGL_NONE
|
|
};
|
|
EGLint w, h;
|
|
EGLint numConfigs;
|
|
EGLConfig config;
|
|
EGLSurface surface;
|
|
EGLContext context;
|
|
|
|
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
|
|
|
eglInitialize(display, 0, 0);
|
|
eglChooseConfig(display, attribs, &config, 1, &numConfigs);
|
|
surface = eglCreateWindowSurface(display, config, s.get(), NULL);
|
|
context = eglCreateContext(display, config, NULL, NULL);
|
|
eglQuerySurface(display, surface, EGL_WIDTH, &w);
|
|
eglQuerySurface(display, surface, EGL_HEIGHT, &h);
|
|
|
|
if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)
|
|
return NO_INIT;
|
|
|
|
mDisplay = display;
|
|
mContext = context;
|
|
mSurface = surface;
|
|
mWidth = w;
|
|
mHeight = h;
|
|
mFlingerSurfaceControl = control;
|
|
mFlingerSurface = s;
|
|
|
|
// If the device has encryption turned on or is in process
|
|
// of being encrypted we show the encrypted boot animation.
|
|
char decrypt[PROPERTY_VALUE_MAX];
|
|
property_get("vold.decrypt", decrypt, "");
|
|
|
|
bool encryptedAnimation = atoi(decrypt) != 0 || !strcmp("trigger_restart_min_framework", decrypt);
|
|
|
|
ZipFileRO* zipFile = NULL;
|
|
if ((encryptedAnimation &&
|
|
(access(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE, R_OK) == 0) &&
|
|
((zipFile = ZipFileRO::open(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE)) != NULL)) ||
|
|
|
|
((access(OEM_BOOTANIMATION_FILE, R_OK) == 0) &&
|
|
((zipFile = ZipFileRO::open(OEM_BOOTANIMATION_FILE)) != NULL)) ||
|
|
|
|
((access(SYSTEM_BOOTANIMATION_FILE, R_OK) == 0) &&
|
|
((zipFile = ZipFileRO::open(SYSTEM_BOOTANIMATION_FILE)) != NULL))) {
|
|
mZip = zipFile;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
bool BootAnimation::threadLoop()
|
|
{
|
|
bool r;
|
|
// We have no bootanimation file, so we use the stock android logo
|
|
// animation.
|
|
if (mZip == NULL) {
|
|
r = android();
|
|
} else {
|
|
r = movie();
|
|
}
|
|
|
|
eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
|
eglDestroyContext(mDisplay, mContext);
|
|
eglDestroySurface(mDisplay, mSurface);
|
|
mFlingerSurface.clear();
|
|
mFlingerSurfaceControl.clear();
|
|
eglTerminate(mDisplay);
|
|
IPCThreadState::self()->stopProcess();
|
|
return r;
|
|
}
|
|
|
|
bool BootAnimation::android()
|
|
{
|
|
initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");
|
|
initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png");
|
|
|
|
// clear screen
|
|
glShadeModel(GL_FLAT);
|
|
glDisable(GL_DITHER);
|
|
glDisable(GL_SCISSOR_TEST);
|
|
glClearColor(0,0,0,1);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
eglSwapBuffers(mDisplay, mSurface);
|
|
|
|
glEnable(GL_TEXTURE_2D);
|
|
glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
|
|
|
const GLint xc = (mWidth - mAndroid[0].w) / 2;
|
|
const GLint yc = (mHeight - mAndroid[0].h) / 2;
|
|
const Rect updateRect(xc, yc, xc + mAndroid[0].w, yc + mAndroid[0].h);
|
|
|
|
glScissor(updateRect.left, mHeight - updateRect.bottom, updateRect.width(),
|
|
updateRect.height());
|
|
|
|
// Blend state
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
|
|
|
const nsecs_t startTime = systemTime();
|
|
do {
|
|
nsecs_t now = systemTime();
|
|
double time = now - startTime;
|
|
float t = 4.0f * float(time / us2ns(16667)) / mAndroid[1].w;
|
|
GLint offset = (1 - (t - floorf(t))) * mAndroid[1].w;
|
|
GLint x = xc - offset;
|
|
|
|
glDisable(GL_SCISSOR_TEST);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
glEnable(GL_SCISSOR_TEST);
|
|
glDisable(GL_BLEND);
|
|
glBindTexture(GL_TEXTURE_2D, mAndroid[1].name);
|
|
glDrawTexiOES(x, yc, 0, mAndroid[1].w, mAndroid[1].h);
|
|
glDrawTexiOES(x + mAndroid[1].w, yc, 0, mAndroid[1].w, mAndroid[1].h);
|
|
|
|
glEnable(GL_BLEND);
|
|
glBindTexture(GL_TEXTURE_2D, mAndroid[0].name);
|
|
glDrawTexiOES(xc, yc, 0, mAndroid[0].w, mAndroid[0].h);
|
|
|
|
EGLBoolean res = eglSwapBuffers(mDisplay, mSurface);
|
|
if (res == EGL_FALSE)
|
|
break;
|
|
|
|
// 12fps: don't animate too fast to preserve CPU
|
|
const nsecs_t sleepTime = 83333 - ns2us(systemTime() - now);
|
|
if (sleepTime > 0)
|
|
usleep(sleepTime);
|
|
|
|
checkExit();
|
|
} while (!exitPending());
|
|
|
|
glDeleteTextures(1, &mAndroid[0].name);
|
|
glDeleteTextures(1, &mAndroid[1].name);
|
|
return false;
|
|
}
|
|
|
|
|
|
void BootAnimation::checkExit() {
|
|
// Allow surface flinger to gracefully request shutdown
|
|
char value[PROPERTY_VALUE_MAX];
|
|
property_get(EXIT_PROP_NAME, value, "0");
|
|
int exitnow = atoi(value);
|
|
if (exitnow) {
|
|
requestExit();
|
|
if (mAudioPlayer != NULL) {
|
|
mAudioPlayer->requestExit();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse a color represented as an HTML-style 'RRGGBB' string: each pair of
|
|
// characters in str is a hex number in [0, 255], which are converted to
|
|
// floating point values in the range [0.0, 1.0] and placed in the
|
|
// corresponding elements of color.
|
|
//
|
|
// If the input string isn't valid, parseColor returns false and color is
|
|
// left unchanged.
|
|
static bool parseColor(const char str[7], float color[3]) {
|
|
float tmpColor[3];
|
|
for (int i = 0; i < 3; i++) {
|
|
int val = 0;
|
|
for (int j = 0; j < 2; j++) {
|
|
val *= 16;
|
|
char c = str[2*i + j];
|
|
if (c >= '0' && c <= '9') val += c - '0';
|
|
else if (c >= 'A' && c <= 'F') val += (c - 'A') + 10;
|
|
else if (c >= 'a' && c <= 'f') val += (c - 'a') + 10;
|
|
else return false;
|
|
}
|
|
tmpColor[i] = static_cast<float>(val) / 255.0f;
|
|
}
|
|
memcpy(color, tmpColor, sizeof(tmpColor));
|
|
return true;
|
|
}
|
|
|
|
bool BootAnimation::readFile(const char* name, String8& outString)
|
|
{
|
|
ZipEntryRO entry = mZip->findEntryByName(name);
|
|
ALOGE_IF(!entry, "couldn't find %s", name);
|
|
if (!entry) {
|
|
return false;
|
|
}
|
|
|
|
FileMap* entryMap = mZip->createEntryFileMap(entry);
|
|
mZip->releaseEntry(entry);
|
|
ALOGE_IF(!entryMap, "entryMap is null");
|
|
if (!entryMap) {
|
|
return false;
|
|
}
|
|
|
|
outString.setTo((char const*)entryMap->getDataPtr(), entryMap->getDataLength());
|
|
delete entryMap;
|
|
return true;
|
|
}
|
|
|
|
bool BootAnimation::movie()
|
|
{
|
|
String8 desString;
|
|
|
|
if (!readFile("desc.txt", desString)) {
|
|
return false;
|
|
}
|
|
char const* s = desString.string();
|
|
|
|
// Create and initialize an AudioPlayer if we have an audio_conf.txt file
|
|
String8 audioConf;
|
|
if (readFile("audio_conf.txt", audioConf)) {
|
|
mAudioPlayer = new AudioPlayer;
|
|
if (!mAudioPlayer->init(audioConf.string())) {
|
|
ALOGE("mAudioPlayer.init failed");
|
|
mAudioPlayer = NULL;
|
|
}
|
|
}
|
|
|
|
Animation animation;
|
|
|
|
// Parse the description file
|
|
for (;;) {
|
|
const char* endl = strstr(s, "\n");
|
|
if (endl == NULL) break;
|
|
String8 line(s, endl - s);
|
|
const char* l = line.string();
|
|
int fps, width, height, count, pause;
|
|
char path[ANIM_ENTRY_NAME_MAX];
|
|
char color[7] = "000000"; // default to black if unspecified
|
|
|
|
char pathType;
|
|
if (sscanf(l, "%d %d %d", &width, &height, &fps) == 3) {
|
|
// ALOGD("> w=%d, h=%d, fps=%d", width, height, fps);
|
|
animation.width = width;
|
|
animation.height = height;
|
|
animation.fps = fps;
|
|
}
|
|
else if (sscanf(l, " %c %d %d %s #%6s", &pathType, &count, &pause, path, color) >= 4) {
|
|
// ALOGD("> type=%c, count=%d, pause=%d, path=%s, color=%s", pathType, count, pause, path, color);
|
|
Animation::Part part;
|
|
part.playUntilComplete = pathType == 'c';
|
|
part.count = count;
|
|
part.pause = pause;
|
|
part.path = path;
|
|
part.audioFile = NULL;
|
|
if (!parseColor(color, part.backgroundColor)) {
|
|
ALOGE("> invalid color '#%s'", color);
|
|
part.backgroundColor[0] = 0.0f;
|
|
part.backgroundColor[1] = 0.0f;
|
|
part.backgroundColor[2] = 0.0f;
|
|
}
|
|
animation.parts.add(part);
|
|
}
|
|
|
|
s = ++endl;
|
|
}
|
|
|
|
// read all the data structures
|
|
const size_t pcount = animation.parts.size();
|
|
void *cookie = NULL;
|
|
if (!mZip->startIteration(&cookie)) {
|
|
return false;
|
|
}
|
|
|
|
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);
|
|
if (foundEntryName > ANIM_ENTRY_NAME_MAX || foundEntryName == -1) {
|
|
ALOGE("Error fetching entry file name");
|
|
continue;
|
|
}
|
|
|
|
const String8 entryName(name);
|
|
const String8 path(entryName.getPathDir());
|
|
const String8 leaf(entryName.getPathLeaf());
|
|
if (leaf.size() > 0) {
|
|
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 (method == ZipFileRO::kCompressStored) {
|
|
FileMap* map = mZip->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;
|
|
} else {
|
|
Animation::Frame frame;
|
|
frame.name = leaf;
|
|
frame.map = map;
|
|
part.frames.add(frame);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mZip->endIteration(cookie);
|
|
|
|
// clear screen
|
|
glShadeModel(GL_FLAT);
|
|
glDisable(GL_DITHER);
|
|
glDisable(GL_SCISSOR_TEST);
|
|
glDisable(GL_BLEND);
|
|
glClearColor(0,0,0,1);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
eglSwapBuffers(mDisplay, mSurface);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
glEnable(GL_TEXTURE_2D);
|
|
glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
|
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
|
|
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));
|
|
|
|
for (size_t i=0 ; i<pcount ; i++) {
|
|
const Animation::Part& part(animation.parts[i]);
|
|
const size_t fcount = part.frames.size();
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
|
|
for (int r=0 ; !part.count || r<part.count ; r++) {
|
|
// Exit any non playuntil complete parts immediately
|
|
if(exitPending() && !part.playUntilComplete)
|
|
break;
|
|
|
|
// only play audio file the first time we animate the part
|
|
if (r == 0 && mAudioPlayer != NULL && part.audioFile) {
|
|
mAudioPlayer->playFile(part.audioFile);
|
|
}
|
|
|
|
glClearColor(
|
|
part.backgroundColor[0],
|
|
part.backgroundColor[1],
|
|
part.backgroundColor[2],
|
|
1.0f);
|
|
|
|
for (size_t j=0 ; j<fcount && (!exitPending() || part.playUntilComplete) ; j++) {
|
|
const Animation::Frame& frame(part.frames[j]);
|
|
nsecs_t lastFrame = systemTime();
|
|
|
|
if (r > 0) {
|
|
glBindTexture(GL_TEXTURE_2D, frame.tid);
|
|
} else {
|
|
if (part.count != 1) {
|
|
glGenTextures(1, &frame.tid);
|
|
glBindTexture(GL_TEXTURE_2D, frame.tid);
|
|
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
}
|
|
initTexture(frame);
|
|
}
|
|
|
|
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());
|
|
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);
|
|
eglSwapBuffers(mDisplay, mSurface);
|
|
|
|
nsecs_t now = systemTime();
|
|
nsecs_t delay = frameDuration - (now - lastFrame);
|
|
//ALOGD("%lld, %lld", ns2ms(now - lastFrame), ns2ms(delay));
|
|
lastFrame = now;
|
|
|
|
if (delay > 0) {
|
|
struct timespec spec;
|
|
spec.tv_sec = (now + delay) / 1000000000;
|
|
spec.tv_nsec = (now + delay) % 1000000000;
|
|
int err;
|
|
do {
|
|
err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &spec, NULL);
|
|
} while (err<0 && errno == EINTR);
|
|
}
|
|
|
|
checkExit();
|
|
}
|
|
|
|
usleep(part.pause * ns2us(frameDuration));
|
|
|
|
// For infinite parts, we've now played them at least once, so perhaps exit
|
|
if(exitPending() && !part.count)
|
|
break;
|
|
}
|
|
|
|
// free the textures for this part
|
|
if (part.count != 1) {
|
|
for (size_t j=0 ; j<fcount ; j++) {
|
|
const Animation::Frame& frame(part.frames[j]);
|
|
glDeleteTextures(1, &frame.tid);
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
}
|
|
; // namespace android
|