/* 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 "android/log.h"
#include "GLContext.h"
#include "gfxPrefs.h"
#include "gfxUtils.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/TouchEvents.h"
#include "mozilla/Hal.h"
#include "libdisplay/BootAnimation.h"
#include "libdisplay/GonkDisplay.h"
#include "nsScreenManagerGonk.h"
#include "nsThreadUtils.h"
#include "HwcComposer2D.h"
#include "VsyncSource.h"
#include "nsWindow.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/layers/CompositorBridgeParent.h"
#include "mozilla/layers/CompositorThread.h"
#include "mozilla/Services.h"
#include "mozilla/ProcessPriorityManager.h"
#include "nsIdleService.h"
#include "nsIObserverService.h"
#include "nsAppShell.h"
#include "nsProxyRelease.h"
#include "nsTArray.h"
#include "pixelflinger/format.h"
#include "nsIDisplayInfo.h"
#include "base/task.h"

#if ANDROID_VERSION >= 17
#include "libdisplay/DisplaySurface.h"
#endif

#define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "nsScreenGonk" , ## args)
#define LOGW(args...) __android_log_print(ANDROID_LOG_WARN, "nsScreenGonk", ## args)
#define LOGE(args...) __android_log_print(ANDROID_LOG_ERROR, "nsScreenGonk", ## args)

using namespace mozilla;
using namespace mozilla::hal;
using namespace mozilla::gfx;
using namespace mozilla::gl;
using namespace mozilla::layers;
using namespace mozilla::dom;

namespace {

class ScreenOnOffEvent : public mozilla::Runnable {
public:
    ScreenOnOffEvent(bool on)
        : mIsOn(on)
    {}

    NS_IMETHOD Run() override {
        // Notify observers that the screen state has just changed.
        nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
        if (observerService) {
          observerService->NotifyObservers(
            nullptr, "screen-state-changed",
            mIsOn ? u"on" : u"off"
          );
        }

        RefPtr<nsScreenGonk> screen = nsScreenManagerGonk::GetPrimaryScreen();
        const nsTArray<nsWindow*>& windows = screen->GetTopWindows();

        for (uint32_t i = 0; i < windows.Length(); i++) {
            nsWindow *win = windows[i];

            if (nsIWidgetListener* listener = win->GetWidgetListener()) {
                listener->SizeModeChanged(mIsOn ? nsSizeMode_Fullscreen : nsSizeMode_Minimized);
            }
        }

        return NS_OK;
    }

private:
    bool mIsOn;
};

static void
displayEnabledCallback(bool enabled)
{
    RefPtr<nsScreenManagerGonk> screenManager = nsScreenManagerGonk::GetInstance();
    screenManager->DisplayEnabled(enabled);
}

} // namespace

static uint32_t
SurfaceFormatToColorDepth(int32_t aSurfaceFormat)
{
    switch (aSurfaceFormat) {
    case GGL_PIXEL_FORMAT_RGB_565:
        return 16;
    case GGL_PIXEL_FORMAT_RGBA_8888:
        return 32;
    }
    return 24; // GGL_PIXEL_FORMAT_RGBX_8888
}

// nsScreenGonk.cpp

nsScreenGonk::nsScreenGonk(uint32_t aId,
                           GonkDisplay::DisplayType aDisplayType,
                           const GonkDisplay::NativeData& aNativeData,
                           NotifyDisplayChangedEvent aEventVisibility)
    : mId(aId)
    , mEventVisibility(aEventVisibility)
    , mNativeWindow(aNativeData.mNativeWindow)
    , mDpi(aNativeData.mXdpi)
    , mScreenRotation(nsIScreen::ROTATION_0_DEG)
    , mPhysicalScreenRotation(nsIScreen::ROTATION_0_DEG)
#if ANDROID_VERSION >= 17
    , mDisplaySurface(aNativeData.mDisplaySurface)
#endif
    , mIsMirroring(false)
    , mDisplayType(aDisplayType)
    , mEGLDisplay(EGL_NO_DISPLAY)
    , mEGLSurface(EGL_NO_SURFACE)
    , mGLContext(nullptr)
    , mFramebuffer(nullptr)
    , mMappedBuffer(nullptr)
{
    if (mNativeWindow->query(mNativeWindow.get(), NATIVE_WINDOW_WIDTH, &mVirtualBounds.width) ||
        mNativeWindow->query(mNativeWindow.get(), NATIVE_WINDOW_HEIGHT, &mVirtualBounds.height) ||
        mNativeWindow->query(mNativeWindow.get(), NATIVE_WINDOW_FORMAT, &mSurfaceFormat)) {
        NS_RUNTIMEABORT("Failed to get native window size, aborting...");
    }

    mNaturalBounds = mVirtualBounds;

    if (IsPrimaryScreen()) {
        char propValue[PROPERTY_VALUE_MAX];
        property_get("ro.sf.hwrotation", propValue, "0");
        mPhysicalScreenRotation = atoi(propValue) / 90;
    }

    mColorDepth = SurfaceFormatToColorDepth(mSurfaceFormat);
}

static void
ReleaseGLContextSync(mozilla::gl::GLContext* aGLContext)
{
    MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
    aGLContext->Release();
}

nsScreenGonk::~nsScreenGonk()
{
    // Release GLContext on compositor thread
    if (mGLContext) {
        CompositorThreadHolder::Loop()->PostTask(
            NewRunnableFunction(&ReleaseGLContextSync,
                                mGLContext.forget().take()));
        mGLContext = nullptr;
    }
}

bool
nsScreenGonk::IsPrimaryScreen()
{
    return mDisplayType == GonkDisplay::DISPLAY_PRIMARY;
}

NS_IMETHODIMP
nsScreenGonk::GetId(uint32_t *outId)
{
    *outId = mId;
    return NS_OK;
}

uint32_t
nsScreenGonk::GetId()
{
    return mId;
}

NotifyDisplayChangedEvent
nsScreenGonk::GetEventVisibility()
{
    return mEventVisibility;
}

NS_IMETHODIMP
nsScreenGonk::GetRect(int32_t *outLeft,  int32_t *outTop,
                      int32_t *outWidth, int32_t *outHeight)
{
    *outLeft = mVirtualBounds.x;
    *outTop = mVirtualBounds.y;

    *outWidth = mVirtualBounds.width;
    *outHeight = mVirtualBounds.height;

    return NS_OK;
}

LayoutDeviceIntRect
nsScreenGonk::GetRect()
{
    return mVirtualBounds;
}

NS_IMETHODIMP
nsScreenGonk::GetAvailRect(int32_t *outLeft,  int32_t *outTop,
                           int32_t *outWidth, int32_t *outHeight)
{
    return GetRect(outLeft, outTop, outWidth, outHeight);
}

NS_IMETHODIMP
nsScreenGonk::GetPixelDepth(int32_t *aPixelDepth)
{
    // XXX: this should actually return 32 when we're using 24-bit
    // color, because we use RGBX.
    *aPixelDepth = mColorDepth;
    return NS_OK;
}

NS_IMETHODIMP
nsScreenGonk::GetColorDepth(int32_t *aColorDepth)
{
    *aColorDepth = mColorDepth;
    return NS_OK;
}

NS_IMETHODIMP
nsScreenGonk::GetRotation(uint32_t* aRotation)
{
    *aRotation = mScreenRotation;
    return NS_OK;
}

float
nsScreenGonk::GetDpi()
{
    return mDpi;
}

int32_t
nsScreenGonk::GetSurfaceFormat()
{
    return mSurfaceFormat;
}

ANativeWindow*
nsScreenGonk::GetNativeWindow()
{
    return mNativeWindow.get();
}

NS_IMETHODIMP
nsScreenGonk::SetRotation(uint32_t aRotation)
{
    if (!(aRotation <= ROTATION_270_DEG)) {
        return NS_ERROR_ILLEGAL_VALUE;
    }

    if (mScreenRotation == aRotation) {
        return NS_OK;
    }

    mScreenRotation = aRotation;
    uint32_t rotation = EffectiveScreenRotation();
    if (rotation == nsIScreen::ROTATION_90_DEG ||
        rotation == nsIScreen::ROTATION_270_DEG) {
        mVirtualBounds = LayoutDeviceIntRect(0, 0,
                                             mNaturalBounds.height,
                                             mNaturalBounds.width);
    } else {
        mVirtualBounds = mNaturalBounds;
    }

    nsAppShell::NotifyScreenRotation();

    for (unsigned int i = 0; i < mTopWindows.Length(); i++) {
        mTopWindows[i]->Resize(mVirtualBounds.width,
                               mVirtualBounds.height,
                               true);
    }

    return NS_OK;
}

LayoutDeviceIntRect
nsScreenGonk::GetNaturalBounds()
{
    return mNaturalBounds;
}

uint32_t
nsScreenGonk::EffectiveScreenRotation()
{
    return (mScreenRotation + mPhysicalScreenRotation) % (360 / 90);
}

// NB: This isn't gonk-specific, but gonk is the only widget backend
// that does this calculation itself, currently.
static ScreenOrientationInternal
ComputeOrientation(uint32_t aRotation, const LayoutDeviceIntSize& aScreenSize)
{
    bool naturallyPortrait = (aScreenSize.height > aScreenSize.width);
    switch (aRotation) {
    case nsIScreen::ROTATION_0_DEG:
        return (naturallyPortrait ? eScreenOrientation_PortraitPrimary :
                eScreenOrientation_LandscapePrimary);
    case nsIScreen::ROTATION_90_DEG:
        // Arbitrarily choosing 90deg to be primary "unnatural"
        // rotation.
        return (naturallyPortrait ? eScreenOrientation_LandscapePrimary :
                eScreenOrientation_PortraitPrimary);
    case nsIScreen::ROTATION_180_DEG:
        return (naturallyPortrait ? eScreenOrientation_PortraitSecondary :
                eScreenOrientation_LandscapeSecondary);
    case nsIScreen::ROTATION_270_DEG:
        return (naturallyPortrait ? eScreenOrientation_LandscapeSecondary :
                eScreenOrientation_PortraitSecondary);
    default:
        MOZ_CRASH("Gonk screen must always have a known rotation");
    }
}

static uint16_t
RotationToAngle(uint32_t aRotation)
{
    uint16_t angle = 90 * aRotation;
    MOZ_ASSERT(angle == 0 || angle == 90 || angle == 180 || angle == 270);
    return angle;
}

ScreenConfiguration
nsScreenGonk::GetConfiguration()
{
    ScreenOrientationInternal orientation =
        ComputeOrientation(mScreenRotation, mNaturalBounds.Size());

    // NB: perpetuating colorDepth == pixelDepth illusion here, for
    // consistency.
    return ScreenConfiguration(mVirtualBounds.ToUnknownRect(), orientation,
                               RotationToAngle(mScreenRotation),
                               mColorDepth, mColorDepth);
}

void
nsScreenGonk::RegisterWindow(nsWindow* aWindow)
{
    mTopWindows.AppendElement(aWindow);
}

void
nsScreenGonk::UnregisterWindow(nsWindow* aWindow)
{
    mTopWindows.RemoveElement(aWindow);
}

void
nsScreenGonk::BringToTop(nsWindow* aWindow)
{
    mTopWindows.RemoveElement(aWindow);
    mTopWindows.InsertElementAt(0, aWindow);
}

static gralloc_module_t const*
gralloc_module()
{
    hw_module_t const *module;
    if (hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module)) {
        return nullptr;
    }
    return reinterpret_cast<gralloc_module_t const*>(module);
}

static SurfaceFormat
HalFormatToSurfaceFormat(int aHalFormat)
{
    switch (aHalFormat) {
    case HAL_PIXEL_FORMAT_RGBA_8888:
        // Needs RB swap
        return SurfaceFormat::B8G8R8A8;
    case HAL_PIXEL_FORMAT_RGBX_8888:
        // Needs RB swap
        return SurfaceFormat::B8G8R8X8;
    case HAL_PIXEL_FORMAT_BGRA_8888:
        return SurfaceFormat::B8G8R8A8;
    case HAL_PIXEL_FORMAT_RGB_565:
        return SurfaceFormat::R5G6B5_UINT16;
    default:
        MOZ_CRASH("Unhandled HAL pixel format");
        return SurfaceFormat::UNKNOWN; // not reached
    }
}

static bool
NeedsRBSwap(int aHalFormat)
{
    switch (aHalFormat) {
    case HAL_PIXEL_FORMAT_RGBA_8888:
        return true;
    case HAL_PIXEL_FORMAT_RGBX_8888:
        return true;
    case HAL_PIXEL_FORMAT_BGRA_8888:
        return false;
    case HAL_PIXEL_FORMAT_RGB_565:
        return false;
    default:
        MOZ_CRASH("Unhandled HAL pixel format");
        return false; // not reached
    }
}

already_AddRefed<DrawTarget>
nsScreenGonk::StartRemoteDrawing()
{
    MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
    MOZ_ASSERT(!mFramebuffer);
    MOZ_ASSERT(!mMappedBuffer);

    mFramebuffer = DequeueBuffer();
    int width = mFramebuffer->width, height = mFramebuffer->height;
    if (gralloc_module()->lock(gralloc_module(), mFramebuffer->handle,
                               GRALLOC_USAGE_SW_READ_NEVER |
                               GRALLOC_USAGE_SW_WRITE_OFTEN |
                               GRALLOC_USAGE_HW_FB,
                               0, 0, width, height,
                               reinterpret_cast<void**>(&mMappedBuffer))) {
        EndRemoteDrawing();
        return nullptr;
    }
    SurfaceFormat format = HalFormatToSurfaceFormat(GetSurfaceFormat());
    mFramebufferTarget = Factory::CreateDrawTargetForData(
        BackendType::CAIRO,
        mMappedBuffer,
        IntSize(width, height),
        mFramebuffer->stride * gfx::BytesPerPixel(format),
        format);
    if (!mFramebufferTarget) {
        MOZ_CRASH("nsWindow::StartRemoteDrawing failed in CreateDrawTargetForData");
    }
    if (!mBackBuffer ||
        mBackBuffer->GetSize() != mFramebufferTarget->GetSize() ||
        mBackBuffer->GetFormat() != mFramebufferTarget->GetFormat()) {
        mBackBuffer = mFramebufferTarget->CreateSimilarDrawTarget(
            mFramebufferTarget->GetSize(), mFramebufferTarget->GetFormat());
    }
    RefPtr<DrawTarget> buffer(mBackBuffer);
    return buffer.forget();
}

void
nsScreenGonk::EndRemoteDrawing()
{
    MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());

    if (mFramebufferTarget && mFramebuffer) {
        IntSize size = mFramebufferTarget->GetSize();
        Rect rect(0, 0, size.width, size.height);
        RefPtr<SourceSurface> source = mBackBuffer->Snapshot();
        mFramebufferTarget->DrawSurface(source, rect, rect);

        // Convert from BGR to RGB
        // XXX this is a temporary solution. It consumes extra cpu cycles,
        // it should not be used on product device.
        if (NeedsRBSwap(GetSurfaceFormat())) {
            LOGE("Very slow composition path, it should not be used on product!!!");
            SurfaceFormat format = HalFormatToSurfaceFormat(GetSurfaceFormat());
            gfxUtils::ConvertBGRAtoRGBA(
                mMappedBuffer,
                mFramebuffer->stride * mFramebuffer->height * gfx::BytesPerPixel(format));
        }
    }
    if (mMappedBuffer) {
        MOZ_ASSERT(mFramebuffer);
        gralloc_module()->unlock(gralloc_module(), mFramebuffer->handle);
        mMappedBuffer = nullptr;
    }
    if (mFramebuffer) {
        QueueBuffer(mFramebuffer);
    }
    mFramebuffer = nullptr;
    mFramebufferTarget = nullptr;
}

ANativeWindowBuffer*
nsScreenGonk::DequeueBuffer()
{
    ANativeWindowBuffer* buf = nullptr;
#if ANDROID_VERSION >= 17
    int fenceFd = -1;
    mNativeWindow->dequeueBuffer(mNativeWindow.get(), &buf, &fenceFd);
    android::sp<android::Fence> fence(new android::Fence(fenceFd));
#if ANDROID_VERSION == 17
    fence->waitForever(1000, "nsScreenGonk_DequeueBuffer");
    // 1000 is what Android uses. It is a warning timeout in ms.
    // This timeout was removed in ANDROID_VERSION 18.
#else
    fence->waitForever("nsScreenGonk_DequeueBuffer");
#endif
#else
    mNativeWindow->dequeueBuffer(mNativeWindow.get(), &buf);
#endif
    return buf;
}

bool
nsScreenGonk::QueueBuffer(ANativeWindowBuffer* buf)
{
#if ANDROID_VERSION >= 17
  int ret = mNativeWindow->queueBuffer(mNativeWindow.get(), buf, -1);
  return ret == 0;
#else
  int ret = mNativeWindow->queueBuffer(mNativeWindow.get(), buf);
  return ret == 0;
#endif
}

nsresult
nsScreenGonk::MakeSnapshot(ANativeWindowBuffer* aBuffer)
{
    MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
    MOZ_ASSERT(aBuffer);

    layers::CompositorBridgeParent* compositorParent = mCompositorBridgeParent;
    if (!compositorParent) {
        return NS_ERROR_FAILURE;
    }

    int width = aBuffer->width, height = aBuffer->height;
    uint8_t* mappedBuffer = nullptr;
    if (gralloc_module()->lock(gralloc_module(), aBuffer->handle,
                               GRALLOC_USAGE_SW_READ_OFTEN |
                               GRALLOC_USAGE_SW_WRITE_OFTEN,
                               0, 0, width, height,
                               reinterpret_cast<void**>(&mappedBuffer))) {
        return NS_ERROR_FAILURE;
    }

    SurfaceFormat format = HalFormatToSurfaceFormat(GetSurfaceFormat());
    RefPtr<DrawTarget> mTarget =
        Factory::CreateDrawTargetForData(
            BackendType::CAIRO,
            mappedBuffer,
            IntSize(width, height),
            aBuffer->stride * gfx::BytesPerPixel(format),
            format);
    if (!mTarget) {
        return NS_ERROR_FAILURE;
    }

    gfx::IntRect rect = GetRect().ToUnknownRect();
    compositorParent->ForceComposeToTarget(mTarget, &rect);

    // Convert from BGR to RGB
    // XXX this is a temporary solution. It consumes extra cpu cycles,
    if (NeedsRBSwap(GetSurfaceFormat())) {
        LOGE("Slow path of making Snapshot!!!");
        SurfaceFormat format = HalFormatToSurfaceFormat(GetSurfaceFormat());
        gfxUtils::ConvertBGRAtoRGBA(
            mappedBuffer,
            aBuffer->stride * aBuffer->height * gfx::BytesPerPixel(format));
        mappedBuffer = nullptr;
    }
    gralloc_module()->unlock(gralloc_module(), aBuffer->handle);
    return NS_OK;
}

void
nsScreenGonk::SetCompositorBridgeParent(layers::CompositorBridgeParent* aCompositorBridgeParent)
{
    MOZ_ASSERT(NS_IsMainThread());
    mCompositorBridgeParent = aCompositorBridgeParent;
}

#if ANDROID_VERSION >= 17
android::DisplaySurface*
nsScreenGonk::GetDisplaySurface()
{
    return mDisplaySurface.get();
}

int
nsScreenGonk::GetPrevDispAcquireFd()
{
    if (!mDisplaySurface.get()) {
        return -1;
    }
    return mDisplaySurface->GetPrevDispAcquireFd();
}
#endif

GonkDisplay::DisplayType
nsScreenGonk::GetDisplayType()
{
    return mDisplayType;
}

void
nsScreenGonk::SetEGLInfo(hwc_display_t aDisplay,
                         hwc_surface_t aSurface,
                         gl::GLContext* aGLContext)
{
    MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
    mEGLDisplay = aDisplay;
    mEGLSurface = aSurface;
    mGLContext = aGLContext;
}

hwc_display_t
nsScreenGonk::GetEGLDisplay()
{
    MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
    return mEGLDisplay;
}

hwc_surface_t
nsScreenGonk::GetEGLSurface()
{
    MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
    return mEGLSurface;
}

already_AddRefed<mozilla::gl::GLContext>
nsScreenGonk::GetGLContext()
{
    MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
    RefPtr<mozilla::gl::GLContext>glContext = mGLContext;
    return glContext.forget();
}

static void
UpdateMirroringWidgetSync(nsMainThreadPtrHandle<nsScreenGonk>&& aScreen, nsWindow* aWindow)
{
    MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
    already_AddRefed<nsWindow> window(aWindow);
    aScreen->UpdateMirroringWidget(window);
}

bool
nsScreenGonk::EnableMirroring()
{
    MOZ_ASSERT(NS_IsMainThread());
    MOZ_ASSERT(!IsPrimaryScreen());

    RefPtr<nsScreenGonk> primaryScreen = nsScreenManagerGonk::GetPrimaryScreen();
    NS_ENSURE_TRUE(primaryScreen, false);

    bool ret = primaryScreen->SetMirroringScreen(this);
    NS_ENSURE_TRUE(ret, false);

    // Create a widget for mirroring
    nsWidgetInitData initData;
    initData.mScreenId = mId;
    RefPtr<nsWindow> window = new nsWindow();
    nsresult rv = window->Create(nullptr, nullptr, mNaturalBounds, &initData);
    NS_ENSURE_SUCCESS(rv, false);
    MOZ_ASSERT(static_cast<nsWindow*>(window)->GetScreen() == this);

    // Update mMirroringWidget on compositor thread
    nsMainThreadPtrHandle<nsScreenGonk> primary =
      nsMainThreadPtrHandle<nsScreenGonk>(new nsMainThreadPtrHolder<nsScreenGonk>(primaryScreen, false));
    CompositorThreadHolder::Loop()->PostTask(
        NewRunnableFunction(&UpdateMirroringWidgetSync,
                            primary,
                            window.forget().take()));

    mIsMirroring = true;
    return true;
}

bool
nsScreenGonk::DisableMirroring()
{
    MOZ_ASSERT(NS_IsMainThread());
    MOZ_ASSERT(!IsPrimaryScreen());

    mIsMirroring = false;
    RefPtr<nsScreenGonk> primaryScreen = nsScreenManagerGonk::GetPrimaryScreen();
    NS_ENSURE_TRUE(primaryScreen, false);

    bool ret = primaryScreen->ClearMirroringScreen(this);
    NS_ENSURE_TRUE(ret, false);

    // Update mMirroringWidget on compositor thread
    nsMainThreadPtrHandle<nsScreenGonk> primary =
      nsMainThreadPtrHandle<nsScreenGonk>(new nsMainThreadPtrHolder<nsScreenGonk>(primaryScreen, false));
    CompositorThreadHolder::Loop()->PostTask(
        NewRunnableFunction(&UpdateMirroringWidgetSync,
                            primary,
                            nullptr));
    return true;
}

bool
nsScreenGonk::SetMirroringScreen(nsScreenGonk* aScreen)
{
    MOZ_ASSERT(NS_IsMainThread());
    MOZ_ASSERT(IsPrimaryScreen());

    if (mMirroringScreen) {
        return false;
    }
    mMirroringScreen = aScreen;
    return true;
}

bool
nsScreenGonk::ClearMirroringScreen(nsScreenGonk* aScreen)
{
    MOZ_ASSERT(NS_IsMainThread());
    MOZ_ASSERT(IsPrimaryScreen());

    if (mMirroringScreen != aScreen) {
        return false;
    }
    mMirroringScreen = nullptr;
    return true;
}

void
nsScreenGonk::UpdateMirroringWidget(already_AddRefed<nsWindow>& aWindow)
{
    MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
    MOZ_ASSERT(IsPrimaryScreen());

    if (mMirroringWidget) {
        nsCOMPtr<nsIWidget> widget = mMirroringWidget.forget();
        NS_ReleaseOnMainThread(widget.forget());
    }
    mMirroringWidget = aWindow;
}

nsWindow*
nsScreenGonk::GetMirroringWidget()
{
    MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
    MOZ_ASSERT(IsPrimaryScreen());

    return mMirroringWidget;
}

NS_IMPL_ISUPPORTS(nsScreenManagerGonk, nsIScreenManager)

nsScreenManagerGonk::nsScreenManagerGonk()
    : mInitialized(false)
#if ANDROID_VERSION >= 19
    , mDisplayEnabled(false)
#endif
{
}

nsScreenManagerGonk::~nsScreenManagerGonk()
{
}

static StaticRefPtr<nsScreenManagerGonk> sScreenManagerGonk;

/* static */ already_AddRefed<nsScreenManagerGonk>
nsScreenManagerGonk::GetInstance()
{
    MOZ_ASSERT(NS_IsMainThread());

    // Avoid creating nsScreenManagerGonk from content process.
    if (!XRE_IsParentProcess()) {
        MOZ_CRASH("Non-chrome processes should not get here.");
    }

    // Avoid creating multiple nsScreenManagerGonk instance inside main process.
    if (!sScreenManagerGonk) {
      sScreenManagerGonk = new nsScreenManagerGonk();
      ClearOnShutdown(&sScreenManagerGonk);
    }

    RefPtr<nsScreenManagerGonk> screenMgr = sScreenManagerGonk.get();
    return screenMgr.forget();
}

/* static */ already_AddRefed< nsScreenGonk>
nsScreenManagerGonk::GetPrimaryScreen()
{
    MOZ_ASSERT(NS_IsMainThread());

    RefPtr<nsScreenManagerGonk> manager = nsScreenManagerGonk::GetInstance();
    nsCOMPtr<nsIScreen> screen;
    manager->GetPrimaryScreen(getter_AddRefs(screen));
    MOZ_ASSERT(screen);
    return already_AddRefed<nsScreenGonk>(
        static_cast<nsScreenGonk*>(screen.forget().take()));
}

void
nsScreenManagerGonk::Initialize()
{
    if (mInitialized) {
        return;
    }

    mScreenOnEvent = new ScreenOnOffEvent(true);
    mScreenOffEvent = new ScreenOnOffEvent(false);
    GetGonkDisplay()->OnEnabled(displayEnabledCallback);

    AddScreen(GonkDisplay::DISPLAY_PRIMARY);

    nsAppShell::NotifyScreenInitialized();
    mInitialized = true;
}

void
nsScreenManagerGonk::DisplayEnabled(bool aEnabled)
{
    MOZ_ASSERT(NS_IsMainThread());

#if ANDROID_VERSION >= 19
    /* Bug 1244044
     * This function could be called before |mCompositorVsyncScheduler| is set.
     * To avoid this issue, keep the value stored in |mDisplayEnabled|.
     */
    mDisplayEnabled = aEnabled;
    if (mCompositorVsyncScheduler) {
        mCompositorVsyncScheduler->SetDisplay(mDisplayEnabled);
    }
#endif

    VsyncControl(aEnabled);
    NS_DispatchToMainThread(aEnabled ? mScreenOnEvent : mScreenOffEvent);
}

NS_IMETHODIMP
nsScreenManagerGonk::GetPrimaryScreen(nsIScreen **outScreen)
{
    NS_IF_ADDREF(*outScreen = mScreens[0].get());
    return NS_OK;
}

NS_IMETHODIMP
nsScreenManagerGonk::ScreenForId(uint32_t aId,
                                 nsIScreen **outScreen)
{
    for (size_t i = 0; i < mScreens.Length(); i++) {
        if (mScreens[i]->GetId() == aId) {
            NS_IF_ADDREF(*outScreen = mScreens[i].get());
            return NS_OK;
        }
    }

    *outScreen = nullptr;
    return NS_OK;
}

NS_IMETHODIMP
nsScreenManagerGonk::ScreenForRect(int32_t inLeft,
                                   int32_t inTop,
                                   int32_t inWidth,
                                   int32_t inHeight,
                                   nsIScreen **outScreen)
{
    // Since all screens have independent coordinate system, we could
    // only return the primary screen no matter what rect is given.
    return GetPrimaryScreen(outScreen);
}

NS_IMETHODIMP
nsScreenManagerGonk::ScreenForNativeWidget(void *aWidget, nsIScreen **outScreen)
{
    for (size_t i = 0; i < mScreens.Length(); i++) {
        if (aWidget == mScreens[i]->GetNativeWindow()) {
            NS_IF_ADDREF(*outScreen = mScreens[i].get());
            return NS_OK;
        }
    }

    *outScreen = nullptr;
    return NS_OK;
}

NS_IMETHODIMP
nsScreenManagerGonk::GetNumberOfScreens(uint32_t *aNumberOfScreens)
{
    *aNumberOfScreens = mScreens.Length();
    return NS_OK;
}

NS_IMETHODIMP
nsScreenManagerGonk::GetSystemDefaultScale(float *aDefaultScale)
{
    *aDefaultScale = 1.0f;
    return NS_OK;
}

void
nsScreenManagerGonk::VsyncControl(bool aEnabled)
{
    if (!NS_IsMainThread()) {
        NS_DispatchToMainThread(
            NewRunnableMethod<bool>(this,
                                    &nsScreenManagerGonk::VsyncControl,
                                    aEnabled));
        return;
    }

    MOZ_ASSERT(NS_IsMainThread());
    VsyncSource::Display &display = gfxPlatform::GetPlatform()->GetHardwareVsync()->GetGlobalDisplay();
    if (aEnabled) {
        display.EnableVsync();
    } else {
        display.DisableVsync();
    }
}

uint32_t
nsScreenManagerGonk::GetIdFromType(GonkDisplay::DisplayType aDisplayType)
{
    // This is the only place where we make the assumption that
    // display type is equivalent to screen id.

    // Bug 1138287 will address the conversion from type to id.
    return aDisplayType;
}

bool
nsScreenManagerGonk::IsScreenConnected(uint32_t aId)
{
    for (size_t i = 0; i < mScreens.Length(); ++i) {
        if (mScreens[i]->GetId() == aId) {
            return true;
        }
    }

    return false;
}

namespace {

// A concrete class as a subject for 'display-changed' observer event.
class DisplayInfo : public nsIDisplayInfo {
public:
    NS_DECL_ISUPPORTS

    DisplayInfo(uint32_t aId, bool aConnected)
        : mId(aId)
        , mConnected(aConnected)
    {
    }

    NS_IMETHODIMP GetId(int32_t *aId)
    {
        *aId = mId;
        return NS_OK;
    }

    NS_IMETHODIMP GetConnected(bool *aConnected)
    {
        *aConnected = mConnected;
        return NS_OK;
    }

private:
    virtual ~DisplayInfo() {}

    uint32_t mId;
    bool mConnected;
};

NS_IMPL_ISUPPORTS(DisplayInfo, nsIDisplayInfo, nsISupports)

class NotifyTask : public mozilla::Runnable {
public:
    NotifyTask(uint32_t aId, bool aConnected)
        : mDisplayInfo(new DisplayInfo(aId, aConnected))
    {
    }

    NS_IMETHOD Run() override
    {
        nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
        if (os) {
          os->NotifyObservers(mDisplayInfo, "display-changed", nullptr);
        }

        return NS_OK;
    }
private:
    RefPtr<DisplayInfo> mDisplayInfo;
};

void
NotifyDisplayChange(uint32_t aId, bool aConnected)
{
    NS_DispatchToMainThread(new NotifyTask(aId, aConnected));
}

} // end of unnamed namespace.

nsresult
nsScreenManagerGonk::AddScreen(GonkDisplay::DisplayType aDisplayType,
                               android::IGraphicBufferProducer* aSink,
                               NotifyDisplayChangedEvent aEventVisibility)
{
    MOZ_ASSERT(NS_IsMainThread());

    NS_ENSURE_TRUE(aDisplayType < GonkDisplay::DisplayType::NUM_DISPLAY_TYPES,
                   NS_ERROR_FAILURE);

    uint32_t id = GetIdFromType(aDisplayType);
    NS_ENSURE_TRUE(!IsScreenConnected(id), NS_ERROR_FAILURE);

    GonkDisplay::NativeData nativeData =
        GetGonkDisplay()->GetNativeData(aDisplayType, aSink);
    nsScreenGonk* screen = new nsScreenGonk(id,
                                            aDisplayType,
                                            nativeData,
                                            aEventVisibility);
    mScreens.AppendElement(screen);

    if (aEventVisibility == NotifyDisplayChangedEvent::Observable) {
        NotifyDisplayChange(id, true);
    }

    // By default, non primary screen does mirroring.
    if (aDisplayType != GonkDisplay::DISPLAY_PRIMARY &&
        gfxPrefs::ScreenMirroringEnabled()) {
        screen->EnableMirroring();
    }

    return NS_OK;
}

nsresult
nsScreenManagerGonk::RemoveScreen(GonkDisplay::DisplayType aDisplayType)
{
    MOZ_ASSERT(NS_IsMainThread());

    NS_ENSURE_TRUE(aDisplayType < GonkDisplay::DisplayType::NUM_DISPLAY_TYPES,
                   NS_ERROR_FAILURE);

    NotifyDisplayChangedEvent eventVisibility = NotifyDisplayChangedEvent::Observable;
    uint32_t screenId = GetIdFromType(aDisplayType);
    NS_ENSURE_TRUE(IsScreenConnected(screenId), NS_ERROR_FAILURE);

    for (size_t i = 0; i < mScreens.Length(); i++) {
        if (mScreens[i]->GetId() == screenId) {
            if (mScreens[i]->IsMirroring()) {
                mScreens[i]->DisableMirroring();
            }
            eventVisibility = mScreens[i]->GetEventVisibility();
            mScreens.RemoveElementAt(i);
            break;
        }
    }

    if (eventVisibility == NotifyDisplayChangedEvent::Observable) {
      NotifyDisplayChange(screenId, false);
    }
    return NS_OK;
}

#if ANDROID_VERSION >= 19
void
nsScreenManagerGonk::SetCompositorVsyncScheduler(mozilla::layers::CompositorVsyncScheduler *aObserver)
{
    MOZ_ASSERT(NS_IsMainThread());

    // We assume on b2g that there is only 1 CompositorBridgeParent
    MOZ_ASSERT(mCompositorVsyncScheduler == nullptr);
    MOZ_ASSERT(aObserver);
    mCompositorVsyncScheduler = aObserver;
    mCompositorVsyncScheduler->SetDisplay(mDisplayEnabled);
}
#endif