Bug 809665 - Boot animation support for B2G, r=cjones
☠☠ backed out by 14f42821a132 ☠ ☠
authorMichael Wu <mwu@mozilla.com>
Fri, 09 Nov 2012 12:08:36 -0800
changeset 123594 d12d632531259718353dc6fe49e98cc08d0331b3
parent 123593 1b6d5e373bcc3c438444946e60581adfa48cd6d9
child 123595 14f42821a132fff2ca6b702f2132532240ea0fc7
push id297
push userlsblakk@mozilla.com
push dateTue, 26 Mar 2013 17:28:00 +0000
treeherdermozilla-release@64d7b45c34e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscjones
bugs809665
milestone20.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 809665 - Boot animation support for B2G, r=cjones
b2g/app/BootAnimation.cpp
b2g/app/BootAnimation.h
b2g/app/Makefile.in
b2g/app/nsBrowserApp.cpp
b2g/chrome/content/shell.js
widget/gonk/Makefile.in
widget/gonk/nsAppShell.cpp
widget/gonk/nsAppShell.h
widget/gonk/nsWindow.cpp
new file mode 100644
--- /dev/null
+++ b/b2g/app/BootAnimation.cpp
@@ -0,0 +1,657 @@
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * 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.
+ */
+
+#include <GLES2/gl2.h>
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include <algorithm>
+#include <endian.h>
+#include <fcntl.h>
+#include <string>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <vector>
+#include "mozilla/Util.h"
+#include "mozilla/NullPtr.h"
+#include "png.h"
+
+#include "android/log.h"
+#include "ui/FramebufferNativeWindow.h"
+#include "hardware_legacy/power.h"
+
+#define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args)
+#define LOGW(args...) __android_log_print(ANDROID_LOG_WARN, "Gonk", ## args)
+#define LOGE(args...) __android_log_print(ANDROID_LOG_ERROR, "Gonk", ## args)
+
+using namespace android;
+using namespace mozilla;
+using namespace std;
+
+static sp<FramebufferNativeWindow> gNativeWindow;
+static pthread_t sAnimationThread;
+static bool sRunAnimation;
+
+/* See http://www.pkware.com/documents/casestudies/APPNOTE.TXT */
+struct local_file_header {
+    uint32_t signature;
+    uint16_t min_version;
+    uint16_t general_flag;
+    uint16_t compression;
+    uint16_t lastmod_time;
+    uint16_t lastmod_date;
+    uint32_t crc32;
+    uint32_t compressed_size;
+    uint32_t uncompressed_size;
+    uint16_t filename_size;
+    uint16_t extra_field_size;
+    char     data[0];
+
+    uint32_t GetDataSize() const
+    {
+        return letoh32(uncompressed_size);
+    }
+
+    uint32_t GetSize() const
+    {
+        /* XXX account for data descriptor */
+        return sizeof(local_file_header) + letoh16(filename_size) +
+               letoh16(extra_field_size) + GetDataSize();
+    }
+
+    const char * GetData() const
+    {
+        return data + letoh16(filename_size) + letoh16(extra_field_size);
+    }
+} __attribute__((__packed__));
+
+struct data_descriptor {
+    uint32_t crc32;
+    uint32_t compressed_size;
+    uint32_t uncompressed_size;
+} __attribute__((__packed__));
+
+struct cdir_entry {
+    uint32_t signature;
+    uint16_t creator_version;
+    uint16_t min_version;
+    uint16_t general_flag;
+    uint16_t compression;
+    uint16_t lastmod_time;
+    uint16_t lastmod_date;
+    uint32_t crc32;
+    uint32_t compressed_size;
+    uint32_t uncompressed_size;
+    uint16_t filename_size;
+    uint16_t extra_field_size;
+    uint16_t file_comment_size;
+    uint16_t disk_num;
+    uint16_t internal_attr;
+    uint32_t external_attr;
+    uint32_t offset;
+    char     data[0];
+
+    uint32_t GetDataSize() const
+    {
+        return letoh32(compressed_size);
+    }
+
+    uint32_t GetSize() const
+    {
+        return sizeof(cdir_entry) + letoh16(filename_size) +
+               letoh16(extra_field_size) + letoh16(file_comment_size);
+    }
+
+    bool Valid() const
+    {
+        return signature == htole32(0x02014b50);
+    }
+} __attribute__((__packed__));
+
+struct cdir_end {
+    uint32_t signature;
+    uint16_t disk_num;
+    uint16_t cdir_disk;
+    uint16_t disk_entries;
+    uint16_t cdir_entries;
+    uint32_t cdir_size;
+    uint32_t cdir_offset;
+    uint16_t comment_size;
+    char     comment[0];
+
+    bool Valid() const
+    {
+        return signature == htole32(0x06054b50);
+    }
+} __attribute__((__packed__));
+
+/* We don't have access to libjar and the zip reader in android
+ * doesn't quite fit what we want to do. */
+class ZipReader {
+    const char *mBuf;
+    const cdir_end *mEnd;
+    const char *mCdir_limit;
+    uint32_t mBuflen;
+
+public:
+    ZipReader() : mBuf(nullptr) {}
+    ~ZipReader() {
+        if (mBuf)
+            munmap((void *)mBuf, mBuflen);
+    }
+
+    bool OpenArchive(const char *path)
+    {
+        int fd;
+        do {
+            fd = open(path, O_RDONLY);
+        } while (fd == -1 && errno == EINTR);
+        if (fd == -1)
+            return false;
+
+        struct stat sb;
+        if (fstat(fd, &sb) == -1 || sb.st_size < sizeof(cdir_end)) {
+            close(fd);
+            return false;
+        }
+
+        mBuflen = sb.st_size;
+        mBuf = (char *)mmap(nullptr, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
+        close(fd);
+
+        if (!mBuf) {
+            return false;
+        }
+
+        madvise(mBuf, sb.st_size, MADV_SEQUENTIAL);
+
+        mEnd = (cdir_end *)(mBuf + mBuflen - sizeof(cdir_end));
+        while (!mEnd->Valid() &&
+               (char *)mEnd > mBuf) {
+            mEnd = (cdir_end *)((char *)mEnd - 1);
+        }
+
+        mCdir_limit = mBuf + letoh32(mEnd->cdir_offset) + letoh32(mEnd->cdir_size);
+
+        if (!mEnd->Valid() || mCdir_limit > (char *)mEnd) {
+            munmap((void *)mBuf, mBuflen);
+            mBuf = nullptr;
+            return false;
+        }
+
+        return true;
+    }
+
+    /* Pass null to get the first cdir entry */
+    const cdir_entry * GetNextEntry(const cdir_entry *prev)
+    {
+        const cdir_entry *entry;
+        if (prev)
+            entry = (cdir_entry *)((char *)prev + prev->GetSize());
+        else
+            entry = (cdir_entry *)(mBuf + letoh32(mEnd->cdir_offset));
+
+        if (((char *)entry + entry->GetSize()) > mCdir_limit ||
+            !entry->Valid())
+            return nullptr;
+        return entry;
+    }
+
+    string GetEntryName(const cdir_entry *entry)
+    {
+        uint16_t len = letoh16(entry->filename_size);
+
+        string name;
+        name.append(entry->data, len);
+        return name;
+    }
+
+    const local_file_header * GetLocalEntry(const cdir_entry *entry)
+    {
+        const local_file_header * data =
+            (local_file_header *)(mBuf + letoh32(entry->offset));
+        if (((char *)data + data->GetSize()) > (char *)mEnd)
+            return nullptr;
+        return data;
+    }
+};
+
+struct AnimationFrame {
+    char path[256];
+    char *buf;
+    uint16_t width;
+    uint16_t height;
+    const local_file_header *file;
+
+    AnimationFrame() : buf(nullptr) {}
+    AnimationFrame(const AnimationFrame &frame) : buf(nullptr) {
+        strncpy(path, frame.path, sizeof(path));
+        file = frame.file;
+    }
+    ~AnimationFrame()
+    {
+        if (buf)
+            free(buf);
+    }
+
+    bool operator<(const AnimationFrame &other) const
+    {
+        return strcmp(path, other.path) < 0;
+    }
+
+    void ReadPngFrame();
+};
+
+struct AnimationPart {
+    int32_t count;
+    int32_t pause;
+    char path[256];
+    vector<AnimationFrame> frames;
+};
+
+using namespace android;
+
+struct RawReadState {
+    const char *start;
+    uint32_t offset;
+    uint32_t length;
+};
+
+static void
+RawReader(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+    RawReadState *state = (RawReadState *)png_get_io_ptr(png_ptr);
+    if (length > (state->length - state->offset))
+        png_error(png_ptr, "Read too long");
+
+    memcpy(data, state->start + state->offset, length);
+    state->offset += length;
+}
+
+void
+AnimationFrame::ReadPngFrame()
+{
+    png_structp pngread = png_create_read_struct(PNG_LIBPNG_VER_STRING,
+                                                 nullptr, nullptr, nullptr);
+
+    png_infop pnginfo = png_create_info_struct(pngread);
+
+    RawReadState state;
+    state.start = file->GetData();
+    state.length = file->GetDataSize();
+    state.offset = 0;
+
+    png_set_read_fn(pngread, &state, RawReader);
+
+    setjmp(png_jmpbuf(pngread));
+
+    png_read_info(pngread, pnginfo);
+
+    width = png_get_image_width(pngread, pnginfo);
+    height = png_get_image_height(pngread, pnginfo);
+    buf = (char *)malloc(width * height * 3);
+
+    vector<char *> rows(height + 1);
+    uint32_t stride = width * 3;
+    for (int i = 0; i < height; i++) {
+        rows[i] = buf + (stride * i);
+    }
+    rows[height] = nullptr;
+    png_set_palette_to_rgb(pngread);
+    png_read_image(pngread, (png_bytepp)&rows.front());
+    png_destroy_read_struct(&pngread, &pnginfo, nullptr);
+}
+
+static const EGLint kEGLConfigAttribs[] = {
+    EGL_SURFACE_TYPE,    EGL_WINDOW_BIT,
+    EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+    EGL_NONE
+};
+
+static bool
+CreateConfig(EGLConfig* aConfig, EGLDisplay display, int format)
+{
+    EGLConfig configs[64];
+    EGLint ncfg = ArrayLength(configs);
+
+    if (!eglChooseConfig(display, kEGLConfigAttribs,
+                         configs, ncfg, &ncfg) ||
+        ncfg < 1) {
+        return false;
+    }
+
+    for (int j = 0; j < ncfg; ++j) {
+        EGLConfig config = configs[j];
+        EGLint id;
+
+        if (eglGetConfigAttrib(display, config,
+                               EGL_NATIVE_VISUAL_ID, &id) &&
+            id > 0 && id == format)
+        {
+            *aConfig = config;
+            return true;
+        }
+    }
+    return false;
+}
+
+static void *
+AnimationThread(void *)
+{
+    ZipReader reader;
+    if (!reader.OpenArchive("/system/media/bootanimation.zip")) {
+        LOGW("Could not open boot animation");
+        return nullptr;
+    }
+
+    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+    eglInitialize(display, nullptr, nullptr);
+
+    int format;
+    ANativeWindow const * const window = gNativeWindow.get();
+    window->query(window, NATIVE_WINDOW_FORMAT, &format);
+
+    EGLConfig config = NULL;
+    CreateConfig(&config, display, format);
+    if (!config) {
+        LOGW("Could not find config for pixel format");
+        return nullptr;
+    }
+
+    EGLSurface surface = eglCreateWindowSurface(display, config, gNativeWindow.get(), nullptr);
+
+    const cdir_entry *entry = nullptr;
+    const local_file_header *file = nullptr;
+    while ((entry = reader.GetNextEntry(entry))) {
+        string name = reader.GetEntryName(entry);
+        if (!name.compare("desc.txt")) {
+            file = reader.GetLocalEntry(entry);
+            break;
+        }
+    }
+
+    if (!file) {
+        LOGW("Could not find desc.txt in boot animation");
+        return nullptr;
+    }
+
+    string descCopy;
+    descCopy.append(file->GetData(), entry->GetDataSize());
+    int32_t width, height, fps;
+    const char *line = descCopy.c_str();
+    const char *end;
+    bool headerRead = true;
+    vector<AnimationPart> parts;
+
+    /*
+     * bootanimation.zip
+     *
+     * This is the boot animation file format that Android uses.
+     * It's a zip file with a directories containing png frames
+     * and a desc.txt that describes how they should be played.
+     *
+     * desc.txt contains two types of lines
+     * 1. [width] [height] [fps]
+     *    There is one of these lines per bootanimation.
+     *    If the width and height are smaller than the screen,
+     *    the frames are centered on a black background.
+     *    XXX: Currently we stretch instead of centering the frame.
+     * 2. p [count] [pause] [path]
+     *    This describes one animation part.
+     *    Each animation part is played in sequence.
+     *    An animation part contains all the files/frames in the
+     *    directory specified in [path]
+     *    [count] indicates the number of times this part repeats.
+     *    [pause] indicates the number of frames that this part
+     *    should pause for after playing the full sequence but
+     *    before repeating.
+     */
+
+    do {
+        end = strstr(line, "\n");
+
+        AnimationPart part;
+        if (headerRead &&
+            sscanf(line, "%d %d %d", &width, &height, &fps) == 3) {
+            headerRead = false;
+        } else if (sscanf(line, "p %d %d %s",
+                          &part.count, &part.pause, part.path)) {
+            parts.push_back(part);
+        }
+    } while (end && *(line = end + 1));
+
+    for (uint32_t i = 0; i < parts.size(); i++) {
+        AnimationPart &part = parts[i];
+        entry = nullptr;
+        char search[256];
+        snprintf(search, sizeof(search), "%s/", part.path);
+        while ((entry = reader.GetNextEntry(entry))) {
+            string name = reader.GetEntryName(entry);
+            if (name.find(search) ||
+                !entry->GetDataSize() ||
+                name.length() >= 256)
+                continue;
+
+            part.frames.push_back();
+            AnimationFrame &frame = part.frames.back();
+            strcpy(frame.path, name.c_str());
+            frame.file = reader.GetLocalEntry(entry);
+        }
+
+        sort(part.frames.begin(), part.frames.end());
+    }
+
+    static EGLint gContextAttribs[] = {
+        EGL_CONTEXT_CLIENT_VERSION, 2,
+        EGL_NONE, 0
+    };
+    EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, gContextAttribs);
+
+    eglMakeCurrent(display, surface, surface, context);
+    glEnable(GL_TEXTURE_2D);
+
+    const char *vsString =
+        "attribute vec2 aPosition; "
+        "attribute vec2 aTexCoord; "
+        "varying vec2 vTexCoord; "
+        "void main() { "
+        "  gl_Position = vec4(aPosition, 0.0, 1.0); "
+        "  vTexCoord = aTexCoord; "
+        "}";
+
+    const char *fsString =
+        "precision mediump float; "
+        "varying vec2 vTexCoord; "
+        "uniform sampler2D sTexture; "
+        "void main() { "
+        "  gl_FragColor = vec4(texture2D(sTexture, vTexCoord).rgb, 1.0); "
+        "}";
+
+    GLint status;
+    GLuint vsh = glCreateShader(GL_VERTEX_SHADER);
+    glShaderSource(vsh, 1, &vsString, nullptr);
+    glCompileShader(vsh);
+    glGetShaderiv(vsh, GL_COMPILE_STATUS, &status);
+    if (!status) {
+        LOGE("Failed to compile vertex shader");
+        return nullptr;
+    }
+
+    GLuint fsh = glCreateShader(GL_FRAGMENT_SHADER);
+    glShaderSource(fsh, 1, &fsString, nullptr);
+    glCompileShader(fsh);
+    glGetShaderiv(fsh, GL_COMPILE_STATUS, &status);
+    if (!status) {
+        LOGE("Failed to compile fragment shader");
+        return nullptr;
+    }
+
+    GLuint programId = glCreateProgram();
+    glAttachShader(programId, vsh);
+    glAttachShader(programId, fsh);
+
+    glLinkProgram(programId);
+    glGetProgramiv(programId, GL_LINK_STATUS, &status);
+    if (!status) {
+        LOG("Failed to link program");
+        return nullptr;
+    }
+
+    GLint positionLoc = glGetAttribLocation(programId, "aPosition");
+    GLint texCoordLoc = glGetAttribLocation(programId, "aTexCoord");
+    GLint textureLoc = glGetUniformLocation(programId, "sTexture");
+
+    glUseProgram(programId);
+
+    GLfloat texCoords[] = { 0.0f, 1.0f,
+                            0.0f, 0.0f,
+                            1.0f, 1.0f,
+                            1.0f, 0.0f };
+
+    GLfloat vCoords[] = { -1.0f, -1.0f,
+                          -1.0f,  1.0f,
+                           1.0f, -1.0f,
+                           1.0f,  1.0f };
+
+    GLuint rectBuf, texBuf;
+    glGenBuffers(1, &rectBuf);
+    glGenBuffers(1, &texBuf);
+
+    GLuint tex;
+    glGenTextures(1, &tex);
+    glActiveTexture(GL_TEXTURE0);
+    glBindTexture(GL_TEXTURE_2D, tex);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+    glEnableVertexAttribArray(positionLoc);
+    glBindBuffer(GL_ARRAY_BUFFER, rectBuf);
+    glBufferData(GL_ARRAY_BUFFER, sizeof(vCoords), vCoords, GL_STATIC_DRAW);
+    glVertexAttribPointer(positionLoc, 2, GL_FLOAT, GL_FALSE, 0, 0);
+
+    glEnableVertexAttribArray(texCoordLoc);
+    glBindBuffer(GL_ARRAY_BUFFER, texBuf);
+    glBufferData(GL_ARRAY_BUFFER, sizeof(texCoords), texCoords, GL_STATIC_DRAW);
+    glVertexAttribPointer(texCoordLoc, 2, GL_FLOAT, GL_FALSE, 0, 0);
+    glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+    glUniform1i(textureLoc, 0);
+
+    uint32_t frameDelayUs = 1000000 / fps;
+
+    for (uint32_t i = 0; i < parts.size(); i++) {
+        AnimationPart &part = parts[i];
+
+        uint32_t j = 0;
+        while (sRunAnimation && (!part.count || j++ < part.count)) {
+            for (uint32_t k = 0; k < part.frames.size(); k++) {
+                struct timeval tv1, tv2;
+                gettimeofday(&tv1, nullptr);
+                AnimationFrame &frame = part.frames[k];
+                if (!frame.buf) {
+                    frame.ReadPngFrame();
+                }
+
+                glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
+                             frame.width, frame.height, 0,
+                             GL_RGB, GL_UNSIGNED_BYTE, frame.buf);
+                glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+                gettimeofday(&tv2, nullptr);
+
+                timersub(&tv2, &tv1, &tv2);
+
+                if (tv2.tv_usec < frameDelayUs) {
+                    usleep(frameDelayUs - tv2.tv_usec);
+                } else {
+                    LOGW("Frame delay is %d us but decoding took %d us", frameDelayUs, tv2.tv_usec);
+                }
+
+                eglSwapBuffers(display, surface);
+
+                if (part.count && j >= part.count) {
+                    free(frame.buf);
+                    frame.buf = nullptr;
+                }
+            }
+            usleep(frameDelayUs * part.pause);
+        }
+    }
+    glBindTexture(GL_TEXTURE_2D, 0);
+    glUseProgram(0);
+    glDeleteTextures(1, &tex);
+    glDeleteBuffers(1, &texBuf);
+    glDeleteBuffers(1, &rectBuf);
+    glDeleteProgram(programId);
+    glDeleteShader(fsh);
+    glDeleteShader(vsh);
+
+    eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+
+    eglDestroyContext(display, context);
+    eglDestroySurface(display, surface);
+    return nullptr;
+}
+
+static int
+CancelBufferNoop(ANativeWindow* aWindow, android_native_buffer_t* aBuffer)
+{
+    return 0;
+}
+
+__attribute__ ((visibility ("default")))
+FramebufferNativeWindow*
+NativeWindow()
+{
+    if (gNativeWindow.get()) {
+        return gNativeWindow.get();
+    }
+
+    // Some gralloc HALs need this in order to open the
+    // framebuffer device after we restart with the screen off.
+    //
+    // NB: this *must* run BEFORE allocating the
+    // FramebufferNativeWindow.  Do not separate these two C++
+    // statements.
+    set_screen_state(1);
+
+    // We (apparently) don't have a way to tell if allocating the
+    // fbs succeeded or failed.
+    gNativeWindow = new FramebufferNativeWindow();
+
+    // Bug 776742: FrambufferNativeWindow doesn't set the cancelBuffer
+    // function pointer, causing EGL to segfault when the window surface
+    // is destroyed (i.e. on process exit). This workaround stops us
+    // from hard crashing in that situation.
+    gNativeWindow->cancelBuffer = CancelBufferNoop;
+
+    sRunAnimation = true;
+    pthread_create(&sAnimationThread, nullptr, AnimationThread, nullptr);
+
+    return gNativeWindow.get();
+}
+
+
+__attribute__ ((visibility ("default")))
+void
+StopBootAnimation()
+{
+    if (sRunAnimation) {
+        sRunAnimation = false;
+        pthread_join(sAnimationThread, nullptr);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/b2g/app/BootAnimation.h
@@ -0,0 +1,17 @@
+#ifndef BOOTANIMATION_H
+#define BOOTANIMATION_H
+
+namespace android {
+class FramebufferNativeWindow;
+}
+
+/* This returns a FramebufferNativeWindow if one exists.
+ * If not, one is created and the boot animation is started. */
+__attribute__ ((weak))
+android::FramebufferNativeWindow* NativeWindow();
+
+/* This stops the boot animation if it's still running. */
+__attribute__ ((weak))
+void StopBootAnimation();
+
+#endif /* BOOTANIMATION_H */
--- a/b2g/app/Makefile.in
+++ b/b2g/app/Makefile.in
@@ -19,16 +19,23 @@ ifndef LIBXUL_SDK
 ifneq ($(GAIADIR),)
 PROGRAM=$(MOZ_APP_NAME)-bin$(BIN_SUFFIX)
 else
 PROGRAM=$(MOZ_APP_NAME)$(BIN_SUFFIX)
 endif
 
 CPPSRCS = nsBrowserApp.cpp
 
+ifeq (gonk,$(MOZ_WIDGET_TOOLKIT))
+CPPSRCS += BootAnimation.cpp
+LIBS += -lpng -lGLESv2 -lEGL -lui -lhardware_legacy -lhardware -lcutils
+LOCAL_INCLUDES += -I$(ANDROID_SOURCE)/external/libpng
+OS_LDFLAGS += -Wl,--export-dynamic
+endif
+
 LOCAL_INCLUDES += -I$(topsrcdir)/toolkit/xre
 LOCAL_INCLUDES += -I$(topsrcdir)/xpcom/base
 LOCAL_INCLUDES += -I$(topsrcdir)/xpcom/build
 LOCAL_INCLUDES += -I$(DEPTH)/build
 
 DEFINES += -DXPCOM_GLUE
 STL_FLAGS=
 
--- a/b2g/app/nsBrowserApp.cpp
+++ b/b2g/app/nsBrowserApp.cpp
@@ -23,16 +23,21 @@
 #include "nsStringGlue.h"
 
 #ifdef XP_WIN
 // we want a wmain entry point
 #include "nsWindowsWMain.cpp"
 #define snprintf _snprintf
 #define strcasecmp _stricmp
 #endif
+
+#ifdef MOZ_WIDGET_GONK
+#include "BootAnimation.h"
+#endif
+
 #include "BinaryPath.h"
 
 #include "nsXPCOMPrivate.h" // for MAXPATHLEN and XPCOM_DLL
 
 #include "mozilla/Telemetry.h"
 
 static void Output(const char *fmt, ... )
 {
@@ -134,16 +139,21 @@ static int do_main(int argc, char* argv[
       Output("Couldn't set %s.\n", appEnv);
       return 255;
     }
     argv[2] = argv[0];
     argv += 2;
     argc -= 2;
   }
 
+#ifdef MOZ_WIDGET_GONK
+  /* Called to start the boot animation */
+  (void) NativeWindow();
+#endif
+
   if (appini) {
     nsXREAppData *appData;
     rv = XRE_CreateAppData(appini, &appData);
     if (NS_FAILED(rv)) {
       Output("Couldn't read application.ini");
       return 255;
     }
     int result = XRE_main(argc, argv, appData, 0);
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -391,16 +391,18 @@ var shell = {
         DOMApplicationRegistry.allAppsLaunchable = true;
 
         this.sendEvent(window, 'ContentStart');
 
         content.addEventListener('load', function shell_homeLoaded() {
           content.removeEventListener('load', shell_homeLoaded);
           shell.isHomeLoaded = true;
 
+          Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
+
           if ('pendingChromeEvents' in shell) {
             shell.pendingChromeEvents.forEach((shell.sendChromeEvent).bind(shell));
           }
           delete shell.pendingChromeEvents;
         });
 
         break;
       case 'MozApplicationManifest':
--- a/widget/gonk/Makefile.in
+++ b/widget/gonk/Makefile.in
@@ -68,16 +68,17 @@ CPPSRCS	= \
 
 SHARED_LIBRARY_LIBS = ../xpwidgets/libxpwidgets_s.a
 
 include $(topsrcdir)/config/rules.mk
 
 DEFINES += -D_IMPL_NS_WIDGET -DHAVE_OFF64_T -DSK_BUILD_FOR_ANDROID_NDK
 
 LOCAL_INCLUDES += \
+	-I$(topsrcdir)/b2g/app \
 	-I$(topsrcdir)/widget/xpwidgets \
 	-I$(topsrcdir)/widget/shared \
 	-I$(topsrcdir)/dom/system/android \
 	-I$(topsrcdir)/content/events/src \
 	-I$(topsrcdir)/gfx/skia/include/core \
 	-I$(topsrcdir)/gfx/skia/include/config \
 	-I$(srcdir) \
 	$(NULL)
--- a/widget/gonk/nsAppShell.cpp
+++ b/widget/gonk/nsAppShell.cpp
@@ -60,22 +60,25 @@
 #else
 # define VERBOSE_LOG(args...)                   \
     (void)0
 #endif
 
 using namespace android;
 using namespace mozilla;
 using namespace mozilla::dom;
+using namespace mozilla::services;
 
 bool gDrawRequest = false;
 static nsAppShell *gAppShell = NULL;
 static int epollfd = 0;
 static int signalfds[2] = {0};
 
+NS_IMPL_ISUPPORTS_INHERITED1(nsAppShell, nsBaseAppShell, nsIObserver)
+
 namespace mozilla {
 
 bool ProcessNextEvent()
 {
     return gAppShell->ProcessNextNativeEvent(true);
 }
 
 void NotifyEvent()
@@ -581,16 +584,17 @@ status_t
 GeckoInputDispatcher::unregisterInputChannel(const sp<InputChannel>& inputChannel)
 {
     return OK;
 }
 
 nsAppShell::nsAppShell()
     : mNativeCallbackRequest(false)
     , mHandlers()
+    , mEnableDraw(false)
 {
     gAppShell = this;
 }
 
 nsAppShell::~nsAppShell()
 {
     // We separate requestExit() and join() here so we can wake the EventHub's
     // input loop, and stop it from polling for input events
@@ -615,26 +619,49 @@ nsAppShell::Init()
     int ret = pipe2(signalfds, O_NONBLOCK);
     NS_ENSURE_FALSE(ret, NS_ERROR_UNEXPECTED);
 
     rv = AddFdHandler(signalfds[0], pipeHandler, "");
     NS_ENSURE_SUCCESS(rv, rv);
 
     InitGonkMemoryPressureMonitoring();
 
+    nsCOMPtr<nsIObserverService> obsServ = GetObserverService();
+    if (obsServ) {
+        obsServ->AddObserver(this, "browser-ui-startup-complete", false);
+    }
+
     // Delay initializing input devices until the screen has been
     // initialized (and we know the resolution).
     return rv;
 }
 
 NS_IMETHODIMP
+nsAppShell::Observe(nsISupports* aSubject,
+                    const char* aTopic,
+                    const PRUnichar* aData)
+{
+    if (strcmp(aTopic, "browser-ui-startup-complete")) {
+        return nsBaseAppShell::Observe(aSubject, aTopic, aData);
+    }
+
+    mEnableDraw = true;
+    NotifyEvent();
+    return NS_OK;
+}
+
+NS_IMETHODIMP
 nsAppShell::Exit()
 {
-  OrientationObserver::ShutDown();
-  return nsBaseAppShell::Exit();
+    OrientationObserver::ShutDown();
+    nsCOMPtr<nsIObserverService> obsServ = GetObserverService();
+    if (obsServ) {
+        obsServ->RemoveObserver(this, "browser-ui-startup-complete");
+    }
+    return nsBaseAppShell::Exit();
 }
 
 void
 nsAppShell::InitInputDevices()
 {
     mEventHub = new EventHub();
     mReaderPolicy = new GeckoInputReaderPolicy();
     mReaderPolicy->setDisplayInfo();
@@ -696,17 +723,17 @@ nsAppShell::ProcessNextNativeEvent(bool 
     // NativeEventCallback always schedules more if it needs it
     // so we can coalesce these.
     // See the implementation in nsBaseAppShell.cpp for more info
     if (mNativeCallbackRequest) {
         mNativeCallbackRequest = false;
         NativeEventCallback();
     }
 
-    if (gDrawRequest) {
+    if (gDrawRequest && mEnableDraw) {
         gDrawRequest = false;
         nsWindow::DoDraw();
     }
 
     return true;
 }
 
 void
--- a/widget/gonk/nsAppShell.h
+++ b/widget/gonk/nsAppShell.h
@@ -59,16 +59,19 @@ class InputReaderThread;
 
 class GeckoInputReaderPolicy;
 class GeckoInputDispatcher;
 
 class nsAppShell : public nsBaseAppShell {
 public:
     nsAppShell();
 
+    NS_DECL_ISUPPORTS_INHERITED
+    NS_DECL_NSIOBSERVER
+
     nsresult Init();
 
     NS_IMETHOD Exit() MOZ_OVERRIDE;
 
     virtual bool ProcessNextNativeEvent(bool maywait);
 
     void NotifyNativeEvent();
 
@@ -82,16 +85,21 @@ protected:
 
 private:
     nsresult AddFdHandler(int fd, FdHandlerCallback handlerFunc,
                           const char* deviceName);
     void InitInputDevices();
 
     // This is somewhat racy but is perfectly safe given how the callback works
     bool mNativeCallbackRequest;
+
+    // This gets flipped when we observe a browser-ui-startup-complete.
+    // browser-ui-startup-complete means that we're really ready to draw
+    // and can stop the boot animation
+    bool mEnableDraw;
     nsTArray<FdHandler> mHandlers;
 
     android::sp<android::EventHub>               mEventHub;
     android::sp<GeckoInputReaderPolicy> mReaderPolicy;
     android::sp<GeckoInputDispatcher>   mDispatcher;
     android::sp<android::InputReader>            mReader;
     android::sp<android::InputReaderThread>      mReaderThread;
 };
--- a/widget/gonk/nsWindow.cpp
+++ b/widget/gonk/nsWindow.cpp
@@ -19,16 +19,17 @@
 
 #include "android/log.h"
 #include "ui/FramebufferNativeWindow.h"
 
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/Hal.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/FileUtils.h"
+#include "BootAnimation.h"
 #include "Framebuffer.h"
 #include "gfxContext.h"
 #include "gfxPlatform.h"
 #include "gfxUtils.h"
 #include "GLContextProvider.h"
 #include "HwcComposer2D.h"
 #include "LayerManagerOGL.h"
 #include "nsAutoPtr.h"
@@ -59,57 +60,25 @@ static uint32_t sScreenRotation;
 static uint32_t sPhysicalScreenRotation;
 static nsIntRect sVirtualBounds;
 static gfxMatrix sRotationMatrix;
 
 static nsRefPtr<GLContext> sGLContext;
 static nsTArray<nsWindow *> sTopWindows;
 static nsWindow *gWindowToRedraw = nullptr;
 static nsWindow *gFocusedWindow = nullptr;
-static android::FramebufferNativeWindow *gNativeWindow = nullptr;
 static bool sFramebufferOpen;
 static bool sUsingOMTC;
 static bool sUsingHwc;
 static bool sScreenInitialized;
 static nsRefPtr<gfxASurface> sOMTCSurface;
 static pthread_t sFramebufferWatchThread;
 
 namespace {
 
-static int
-CancelBufferNoop(ANativeWindow* aWindow, android_native_buffer_t* aBuffer)
-{
-    return 0;
-}
-
-android::FramebufferNativeWindow*
-NativeWindow()
-{
-    if (!gNativeWindow) {
-        // Some gralloc HALs need this in order to open the
-        // framebuffer device after we restart with the screen off.
-        //
-        // NB: this *must* run BEFORE allocating the
-        // FramebufferNativeWindow.  Do not separate these two C++
-        // statements.
-        hal::SetScreenEnabled(true);
-
-        // We (apparently) don't have a way to tell if allocating the
-        // fbs succeeded or failed.
-        gNativeWindow = new android::FramebufferNativeWindow();
-
-        // Bug 776742: FrambufferNativeWindow doesn't set the cancelBuffer
-        // function pointer, causing EGL to segfault when the window surface
-        // is destroyed (i.e. on process exit). This workaround stops us
-        // from hard crashing in that situation.
-        gNativeWindow->cancelBuffer = CancelBufferNoop;
-    }
-    return gNativeWindow;
-}
-
 static uint32_t
 EffectiveScreenRotation()
 {
     return (sScreenRotation + sPhysicalScreenRotation) % (360 / 90);
 }
 
 class ScreenOnOffEvent : public nsRunnable {
 public:
@@ -238,16 +207,18 @@ nsWindow::DoDraw(void)
         return;
     }
 
     if (!gWindowToRedraw) {
         LOG("  no window to draw, bailing");
         return;
     }
 
+    StopBootAnimation();
+
     nsIntRegion region = gWindowToRedraw->mDirtyRegion;
     gWindowToRedraw->mDirtyRegion.SetEmpty();
 
     LayerManager* lm = gWindowToRedraw->GetLayerManager();
     if (mozilla::layers::LAYERS_OPENGL == lm->GetBackendType()) {
         LayerManagerOGL* oglm = static_cast<LayerManagerOGL*>(lm);
         oglm->SetClippingRegion(region);
         oglm->SetWorldTransform(sRotationMatrix);