michael@0: /* Copyright 2012 Mozilla Foundation and Mozilla contributors michael@0: * michael@0: * Licensed under the Apache License, Version 2.0 (the "License"); michael@0: * you may not use this file except in compliance with the License. michael@0: * You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, software michael@0: * distributed under the License is distributed on an "AS IS" BASIS, michael@0: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. michael@0: * See the License for the specific language governing permissions and michael@0: * limitations under the License. michael@0: */ michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include "mozilla/FileUtils.h" michael@0: #include "mozilla/NullPtr.h" michael@0: #include "png.h" michael@0: michael@0: #include "android/log.h" michael@0: #include "GonkDisplay.h" michael@0: #include "hardware/gralloc.h" michael@0: michael@0: #define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args) michael@0: #define LOGW(args...) __android_log_print(ANDROID_LOG_WARN, "Gonk", ## args) michael@0: #define LOGE(args...) __android_log_print(ANDROID_LOG_ERROR, "Gonk", ## args) michael@0: michael@0: using namespace mozilla; michael@0: using namespace std; michael@0: michael@0: static pthread_t sAnimationThread; michael@0: static bool sRunAnimation; michael@0: michael@0: /* See http://www.pkware.com/documents/casestudies/APPNOTE.TXT */ michael@0: struct local_file_header { michael@0: uint32_t signature; michael@0: uint16_t min_version; michael@0: uint16_t general_flag; michael@0: uint16_t compression; michael@0: uint16_t lastmod_time; michael@0: uint16_t lastmod_date; michael@0: uint32_t crc32; michael@0: uint32_t compressed_size; michael@0: uint32_t uncompressed_size; michael@0: uint16_t filename_size; michael@0: uint16_t extra_field_size; michael@0: char data[0]; michael@0: michael@0: uint32_t GetDataSize() const michael@0: { michael@0: return letoh32(uncompressed_size); michael@0: } michael@0: michael@0: uint32_t GetSize() const michael@0: { michael@0: /* XXX account for data descriptor */ michael@0: return sizeof(local_file_header) + letoh16(filename_size) + michael@0: letoh16(extra_field_size) + GetDataSize(); michael@0: } michael@0: michael@0: const char * GetData() const michael@0: { michael@0: return data + letoh16(filename_size) + letoh16(extra_field_size); michael@0: } michael@0: } __attribute__((__packed__)); michael@0: michael@0: struct data_descriptor { michael@0: uint32_t crc32; michael@0: uint32_t compressed_size; michael@0: uint32_t uncompressed_size; michael@0: } __attribute__((__packed__)); michael@0: michael@0: struct cdir_entry { michael@0: uint32_t signature; michael@0: uint16_t creator_version; michael@0: uint16_t min_version; michael@0: uint16_t general_flag; michael@0: uint16_t compression; michael@0: uint16_t lastmod_time; michael@0: uint16_t lastmod_date; michael@0: uint32_t crc32; michael@0: uint32_t compressed_size; michael@0: uint32_t uncompressed_size; michael@0: uint16_t filename_size; michael@0: uint16_t extra_field_size; michael@0: uint16_t file_comment_size; michael@0: uint16_t disk_num; michael@0: uint16_t internal_attr; michael@0: uint32_t external_attr; michael@0: uint32_t offset; michael@0: char data[0]; michael@0: michael@0: uint32_t GetDataSize() const michael@0: { michael@0: return letoh32(compressed_size); michael@0: } michael@0: michael@0: uint32_t GetSize() const michael@0: { michael@0: return sizeof(cdir_entry) + letoh16(filename_size) + michael@0: letoh16(extra_field_size) + letoh16(file_comment_size); michael@0: } michael@0: michael@0: bool Valid() const michael@0: { michael@0: return signature == htole32(0x02014b50); michael@0: } michael@0: } __attribute__((__packed__)); michael@0: michael@0: struct cdir_end { michael@0: uint32_t signature; michael@0: uint16_t disk_num; michael@0: uint16_t cdir_disk; michael@0: uint16_t disk_entries; michael@0: uint16_t cdir_entries; michael@0: uint32_t cdir_size; michael@0: uint32_t cdir_offset; michael@0: uint16_t comment_size; michael@0: char comment[0]; michael@0: michael@0: bool Valid() const michael@0: { michael@0: return signature == htole32(0x06054b50); michael@0: } michael@0: } __attribute__((__packed__)); michael@0: michael@0: /* We don't have access to libjar and the zip reader in android michael@0: * doesn't quite fit what we want to do. */ michael@0: class ZipReader { michael@0: const char *mBuf; michael@0: const cdir_end *mEnd; michael@0: const char *mCdir_limit; michael@0: uint32_t mBuflen; michael@0: michael@0: public: michael@0: ZipReader() : mBuf(nullptr) {} michael@0: ~ZipReader() { michael@0: if (mBuf) michael@0: munmap((void *)mBuf, mBuflen); michael@0: } michael@0: michael@0: bool OpenArchive(const char *path) michael@0: { michael@0: int fd; michael@0: do { michael@0: fd = open(path, O_RDONLY); michael@0: } while (fd == -1 && errno == EINTR); michael@0: if (fd == -1) michael@0: return false; michael@0: michael@0: struct stat sb; michael@0: if (fstat(fd, &sb) == -1 || sb.st_size < sizeof(cdir_end)) { michael@0: close(fd); michael@0: return false; michael@0: } michael@0: michael@0: mBuflen = sb.st_size; michael@0: mBuf = (char *)mmap(nullptr, sb.st_size, PROT_READ, MAP_SHARED, fd, 0); michael@0: close(fd); michael@0: michael@0: if (!mBuf) { michael@0: return false; michael@0: } michael@0: michael@0: madvise(mBuf, sb.st_size, MADV_SEQUENTIAL); michael@0: michael@0: mEnd = (cdir_end *)(mBuf + mBuflen - sizeof(cdir_end)); michael@0: while (!mEnd->Valid() && michael@0: (char *)mEnd > mBuf) { michael@0: mEnd = (cdir_end *)((char *)mEnd - 1); michael@0: } michael@0: michael@0: mCdir_limit = mBuf + letoh32(mEnd->cdir_offset) + letoh32(mEnd->cdir_size); michael@0: michael@0: if (!mEnd->Valid() || mCdir_limit > (char *)mEnd) { michael@0: munmap((void *)mBuf, mBuflen); michael@0: mBuf = nullptr; michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* Pass null to get the first cdir entry */ michael@0: const cdir_entry * GetNextEntry(const cdir_entry *prev) michael@0: { michael@0: const cdir_entry *entry; michael@0: if (prev) michael@0: entry = (cdir_entry *)((char *)prev + prev->GetSize()); michael@0: else michael@0: entry = (cdir_entry *)(mBuf + letoh32(mEnd->cdir_offset)); michael@0: michael@0: if (((char *)entry + entry->GetSize()) > mCdir_limit || michael@0: !entry->Valid()) michael@0: return nullptr; michael@0: return entry; michael@0: } michael@0: michael@0: string GetEntryName(const cdir_entry *entry) michael@0: { michael@0: uint16_t len = letoh16(entry->filename_size); michael@0: michael@0: string name; michael@0: name.append(entry->data, len); michael@0: return name; michael@0: } michael@0: michael@0: const local_file_header * GetLocalEntry(const cdir_entry *entry) michael@0: { michael@0: const local_file_header * data = michael@0: (local_file_header *)(mBuf + letoh32(entry->offset)); michael@0: if (((char *)data + data->GetSize()) > (char *)mEnd) michael@0: return nullptr; michael@0: return data; michael@0: } michael@0: }; michael@0: michael@0: struct AnimationFrame { michael@0: char path[256]; michael@0: char *buf; michael@0: const local_file_header *file; michael@0: uint32_t width; michael@0: uint32_t height; michael@0: uint16_t bytepp; michael@0: michael@0: AnimationFrame() : buf(nullptr) {} michael@0: AnimationFrame(const AnimationFrame &frame) : buf(nullptr) { michael@0: strncpy(path, frame.path, sizeof(path)); michael@0: file = frame.file; michael@0: } michael@0: ~AnimationFrame() michael@0: { michael@0: if (buf) michael@0: free(buf); michael@0: } michael@0: michael@0: bool operator<(const AnimationFrame &other) const michael@0: { michael@0: return strcmp(path, other.path) < 0; michael@0: } michael@0: michael@0: void ReadPngFrame(int outputFormat); michael@0: }; michael@0: michael@0: struct AnimationPart { michael@0: int32_t count; michael@0: int32_t pause; michael@0: char path[256]; michael@0: vector frames; michael@0: }; michael@0: michael@0: struct RawReadState { michael@0: const char *start; michael@0: uint32_t offset; michael@0: uint32_t length; michael@0: }; michael@0: michael@0: static void michael@0: RawReader(png_structp png_ptr, png_bytep data, png_size_t length) michael@0: { michael@0: RawReadState *state = (RawReadState *)png_get_io_ptr(png_ptr); michael@0: if (length > (state->length - state->offset)) michael@0: png_error(png_ptr, "PNG read overrun"); michael@0: michael@0: memcpy(data, state->start + state->offset, length); michael@0: state->offset += length; michael@0: } michael@0: michael@0: static void michael@0: TransformTo565(png_structp png_ptr, png_row_infop row_info, png_bytep data) michael@0: { michael@0: uint16_t *outbuf = (uint16_t *)data; michael@0: uint8_t *inbuf = (uint8_t *)data; michael@0: for (int i = 0; i < row_info->rowbytes; i += 3) { michael@0: *outbuf++ = ((inbuf[i] & 0xF8) << 8) | michael@0: ((inbuf[i + 1] & 0xFC) << 3) | michael@0: ((inbuf[i + 2] ) >> 3); michael@0: } michael@0: } michael@0: michael@0: void michael@0: AnimationFrame::ReadPngFrame(int outputFormat) michael@0: { michael@0: #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED michael@0: static const png_byte unused_chunks[] = michael@0: { 98, 75, 71, 68, '\0', /* bKGD */ michael@0: 99, 72, 82, 77, '\0', /* cHRM */ michael@0: 104, 73, 83, 84, '\0', /* hIST */ michael@0: 105, 67, 67, 80, '\0', /* iCCP */ michael@0: 105, 84, 88, 116, '\0', /* iTXt */ michael@0: 111, 70, 70, 115, '\0', /* oFFs */ michael@0: 112, 67, 65, 76, '\0', /* pCAL */ michael@0: 115, 67, 65, 76, '\0', /* sCAL */ michael@0: 112, 72, 89, 115, '\0', /* pHYs */ michael@0: 115, 66, 73, 84, '\0', /* sBIT */ michael@0: 115, 80, 76, 84, '\0', /* sPLT */ michael@0: 116, 69, 88, 116, '\0', /* tEXt */ michael@0: 116, 73, 77, 69, '\0', /* tIME */ michael@0: 122, 84, 88, 116, '\0'}; /* zTXt */ michael@0: static const png_byte tRNS_chunk[] = michael@0: {116, 82, 78, 83, '\0'}; /* tRNS */ michael@0: #endif michael@0: michael@0: png_structp pngread = png_create_read_struct(PNG_LIBPNG_VER_STRING, michael@0: nullptr, nullptr, nullptr); michael@0: michael@0: if (!pngread) michael@0: return; michael@0: michael@0: png_infop pnginfo = png_create_info_struct(pngread); michael@0: michael@0: if (!pnginfo) { michael@0: png_destroy_read_struct(&pngread, &pnginfo, nullptr); michael@0: return; michael@0: } michael@0: michael@0: if (setjmp(png_jmpbuf(pngread))) { michael@0: // libpng reported an error and longjumped here. Clean up and return. michael@0: png_destroy_read_struct(&pngread, &pnginfo, nullptr); michael@0: return; michael@0: } michael@0: michael@0: RawReadState state; michael@0: state.start = file->GetData(); michael@0: state.length = file->GetDataSize(); michael@0: state.offset = 0; michael@0: michael@0: png_set_read_fn(pngread, &state, RawReader); michael@0: michael@0: #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED michael@0: /* Ignore unused chunks */ michael@0: png_set_keep_unknown_chunks(pngread, 1, unused_chunks, michael@0: (int)sizeof(unused_chunks)/5); michael@0: michael@0: /* Ignore the tRNS chunk if we only want opaque output */ michael@0: if (outputFormat == HAL_PIXEL_FORMAT_RGB_888 || michael@0: outputFormat == HAL_PIXEL_FORMAT_RGB_565) { michael@0: png_set_keep_unknown_chunks(pngread, 1, tRNS_chunk, 1); michael@0: } michael@0: #endif michael@0: michael@0: png_read_info(pngread, pnginfo); michael@0: michael@0: width = png_get_image_width(pngread, pnginfo); michael@0: height = png_get_image_height(pngread, pnginfo); michael@0: michael@0: switch (outputFormat) { michael@0: case HAL_PIXEL_FORMAT_BGRA_8888: michael@0: png_set_bgr(pngread); michael@0: // FALL THROUGH michael@0: case HAL_PIXEL_FORMAT_RGBA_8888: michael@0: case HAL_PIXEL_FORMAT_RGBX_8888: michael@0: bytepp = 4; michael@0: png_set_filler(pngread, 0xFF, PNG_FILLER_AFTER); michael@0: break; michael@0: case HAL_PIXEL_FORMAT_RGB_888: michael@0: bytepp = 3; michael@0: png_set_strip_alpha(pngread); michael@0: break; michael@0: default: michael@0: LOGW("Unknown pixel format %d. Assuming RGB 565.", outputFormat); michael@0: // FALL THROUGH michael@0: case HAL_PIXEL_FORMAT_RGB_565: michael@0: bytepp = 2; michael@0: png_set_strip_alpha(pngread); michael@0: png_set_read_user_transform_fn(pngread, TransformTo565); michael@0: break; michael@0: } michael@0: michael@0: // An extra row is added to give libpng enough space when michael@0: // decoding 3/4 bytepp inputs for 2 bytepp output surfaces michael@0: buf = (char *)malloc(width * (height + 1) * bytepp); michael@0: michael@0: vector rows(height + 1); michael@0: uint32_t stride = width * bytepp; michael@0: for (int i = 0; i < height; i++) { michael@0: rows[i] = buf + (stride * i); michael@0: } michael@0: rows[height] = nullptr; michael@0: png_set_strip_16(pngread); michael@0: png_set_palette_to_rgb(pngread); michael@0: png_set_gray_to_rgb(pngread); michael@0: png_read_image(pngread, (png_bytepp)&rows.front()); michael@0: png_destroy_read_struct(&pngread, &pnginfo, nullptr); michael@0: } michael@0: michael@0: static void * michael@0: AnimationThread(void *) michael@0: { michael@0: ZipReader reader; michael@0: if (!reader.OpenArchive("/system/media/bootanimation.zip")) { michael@0: LOGW("Could not open boot animation"); michael@0: return nullptr; michael@0: } michael@0: michael@0: const cdir_entry *entry = nullptr; michael@0: const local_file_header *file = nullptr; michael@0: while ((entry = reader.GetNextEntry(entry))) { michael@0: string name = reader.GetEntryName(entry); michael@0: if (!name.compare("desc.txt")) { michael@0: file = reader.GetLocalEntry(entry); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (!file) { michael@0: LOGW("Could not find desc.txt in boot animation"); michael@0: return nullptr; michael@0: } michael@0: michael@0: GonkDisplay *display = GetGonkDisplay(); michael@0: int format = display->surfaceformat; michael@0: michael@0: hw_module_t const *module; michael@0: if (hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module)) { michael@0: LOGW("Could not get gralloc module"); michael@0: return nullptr; michael@0: } michael@0: gralloc_module_t const *grmodule = michael@0: reinterpret_cast(module); michael@0: michael@0: string descCopy; michael@0: descCopy.append(file->GetData(), entry->GetDataSize()); michael@0: int32_t width, height, fps; michael@0: const char *line = descCopy.c_str(); michael@0: const char *end; michael@0: bool headerRead = true; michael@0: vector parts; michael@0: michael@0: /* michael@0: * bootanimation.zip michael@0: * michael@0: * This is the boot animation file format that Android uses. michael@0: * It's a zip file with a directories containing png frames michael@0: * and a desc.txt that describes how they should be played. michael@0: * michael@0: * desc.txt contains two types of lines michael@0: * 1. [width] [height] [fps] michael@0: * There is one of these lines per bootanimation. michael@0: * If the width and height are smaller than the screen, michael@0: * the frames are centered on a black background. michael@0: * XXX: Currently we stretch instead of centering the frame. michael@0: * 2. p [count] [pause] [path] michael@0: * This describes one animation part. michael@0: * Each animation part is played in sequence. michael@0: * An animation part contains all the files/frames in the michael@0: * directory specified in [path] michael@0: * [count] indicates the number of times this part repeats. michael@0: * [pause] indicates the number of frames that this part michael@0: * should pause for after playing the full sequence but michael@0: * before repeating. michael@0: */ michael@0: michael@0: do { michael@0: end = strstr(line, "\n"); michael@0: michael@0: AnimationPart part; michael@0: if (headerRead && michael@0: sscanf(line, "%d %d %d", &width, &height, &fps) == 3) { michael@0: headerRead = false; michael@0: } else if (sscanf(line, "p %d %d %s", michael@0: &part.count, &part.pause, part.path)) { michael@0: parts.push_back(part); michael@0: } michael@0: } while (end && *(line = end + 1)); michael@0: michael@0: for (uint32_t i = 0; i < parts.size(); i++) { michael@0: AnimationPart &part = parts[i]; michael@0: entry = nullptr; michael@0: char search[256]; michael@0: snprintf(search, sizeof(search), "%s/", part.path); michael@0: while ((entry = reader.GetNextEntry(entry))) { michael@0: string name = reader.GetEntryName(entry); michael@0: if (name.find(search) || michael@0: !entry->GetDataSize() || michael@0: name.length() >= 256) michael@0: continue; michael@0: michael@0: part.frames.push_back(); michael@0: AnimationFrame &frame = part.frames.back(); michael@0: strcpy(frame.path, name.c_str()); michael@0: frame.file = reader.GetLocalEntry(entry); michael@0: } michael@0: michael@0: sort(part.frames.begin(), part.frames.end()); michael@0: } michael@0: michael@0: uint32_t frameDelayUs = 1000000 / fps; michael@0: michael@0: for (uint32_t i = 0; i < parts.size(); i++) { michael@0: AnimationPart &part = parts[i]; michael@0: michael@0: uint32_t j = 0; michael@0: while (sRunAnimation && (!part.count || j++ < part.count)) { michael@0: for (uint32_t k = 0; k < part.frames.size(); k++) { michael@0: struct timeval tv1, tv2; michael@0: gettimeofday(&tv1, nullptr); michael@0: AnimationFrame &frame = part.frames[k]; michael@0: if (!frame.buf) { michael@0: frame.ReadPngFrame(format); michael@0: } michael@0: michael@0: ANativeWindowBuffer *buf = display->DequeueBuffer(); michael@0: if (!buf) { michael@0: LOGW("Failed to get an ANativeWindowBuffer"); michael@0: break; michael@0: } michael@0: michael@0: void *vaddr; michael@0: if (grmodule->lock(grmodule, buf->handle, michael@0: GRALLOC_USAGE_SW_READ_NEVER | michael@0: GRALLOC_USAGE_SW_WRITE_OFTEN | michael@0: GRALLOC_USAGE_HW_FB, michael@0: 0, 0, width, height, &vaddr)) { michael@0: LOGW("Failed to lock buffer_handle_t"); michael@0: display->QueueBuffer(buf); michael@0: break; michael@0: } michael@0: michael@0: if (buf->height == frame.height && buf->width == frame.width) { michael@0: memcpy(vaddr, frame.buf, michael@0: frame.width * frame.height * frame.bytepp); michael@0: } else if (buf->height >= frame.height && michael@0: buf->width >= frame.width) { michael@0: int startx = (buf->width - frame.width) / 2; michael@0: int starty = (buf->height - frame.height) / 2; michael@0: michael@0: int src_stride = frame.width * frame.bytepp; michael@0: int dst_stride = buf->stride * frame.bytepp; michael@0: michael@0: char *src = frame.buf; michael@0: char *dst = (char *) vaddr + starty * dst_stride + startx * frame.bytepp; michael@0: michael@0: for (int i = 0; i < frame.height; i++) { michael@0: memcpy(dst, src, src_stride); michael@0: src += src_stride; michael@0: dst += dst_stride; michael@0: } michael@0: } michael@0: grmodule->unlock(grmodule, buf->handle); michael@0: michael@0: gettimeofday(&tv2, nullptr); michael@0: michael@0: timersub(&tv2, &tv1, &tv2); michael@0: michael@0: if (tv2.tv_usec < frameDelayUs) { michael@0: usleep(frameDelayUs - tv2.tv_usec); michael@0: } else { michael@0: LOGW("Frame delay is %d us but decoding took %d us", michael@0: frameDelayUs, tv2.tv_usec); michael@0: } michael@0: michael@0: display->QueueBuffer(buf); michael@0: michael@0: if (part.count && j >= part.count) { michael@0: free(frame.buf); michael@0: frame.buf = nullptr; michael@0: } michael@0: } michael@0: usleep(frameDelayUs * part.pause); michael@0: } michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: StartBootAnimation() michael@0: { michael@0: sRunAnimation = true; michael@0: pthread_create(&sAnimationThread, nullptr, AnimationThread, nullptr); michael@0: } michael@0: michael@0: michael@0: void michael@0: StopBootAnimation() michael@0: { michael@0: if (sRunAnimation) { michael@0: sRunAnimation = false; michael@0: pthread_join(sAnimationThread, nullptr); michael@0: } michael@0: }