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 119308 d12d632531259718353dc6fe49e98cc08d0331b3
parent 119307 1b6d5e373bcc3c438444946e60581adfa48cd6d9
child 119309 14f42821a132fff2ca6b702f2132532240ea0fc7
push idunknown
push userunknown
push dateunknown
reviewerscjones
bugs809665
milestone20.0a1
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);