Bug 1148149 - Support Android Presentation API. r=snorp, r=jgilbert
☠☠ backed out by a1d6b3af4d41 ☠ ☠
authorRandall Barker <rbarker@mozilla.com>
Wed, 08 Apr 2015 16:00:00 -0400
changeset 238634 9271d92ee0e20cc50596d2bfd0ed32cc51037e53
parent 238633 484fe7759dd4f735bc455f65e81e3b545cfca810
child 238635 6d49087d2a35bd63dce9a56239a21661f7b2c97a
push idunknown
push userunknown
push dateunknown
reviewerssnorp, jgilbert
bugs1148149
milestone40.0a1
Bug 1148149 - Support Android Presentation API. r=snorp, r=jgilbert
gfx/gl/GLContextProviderEGL.cpp
gfx/gl/GLContextProviderImpl.h
gfx/layers/composite/LayerManagerComposite.cpp
gfx/layers/composite/LayerManagerComposite.h
gfx/layers/ipc/CompositorParent.cpp
gfx/layers/ipc/CompositorParent.h
gfx/layers/opengl/CompositorOGL.h
mobile/android/base/GeckoAppShell.java
mobile/android/base/MediaPlayerManager.java
mozglue/android/jni-stubs.inc
widget/android/AndroidBridge.cpp
widget/android/AndroidBridge.h
widget/android/AndroidJNI.cpp
widget/android/nsWindow.cpp
widget/android/nsWindow.h
--- a/gfx/gl/GLContextProviderEGL.cpp
+++ b/gfx/gl/GLContextProviderEGL.cpp
@@ -441,16 +441,19 @@ GLContextEGL::RenewSurface() {
     return MakeCurrent(true);
 }
 
 void
 GLContextEGL::ReleaseSurface() {
     if (mOwnsContext) {
         mozilla::gl::DestroySurface(mSurface);
     }
+    if (mSurface == mSurfaceOverride) {
+        mSurfaceOverride = EGL_NO_SURFACE;
+    }
     mSurface = EGL_NO_SURFACE;
 }
 
 bool
 GLContextEGL::SetupLookupFunction()
 {
     mLookupFunc = (PlatformLookupFunction)sEGLLibrary.mSymbols.fGetProcAddress;
     return true;
@@ -808,16 +811,51 @@ GLContextProviderEGL::CreateForWindow(ns
     }
 
     glContext->MakeCurrent();
     glContext->SetIsDoubleBuffered(doubleBuffered);
 
     return glContext.forget();
 }
 
+#if defined(ANDROID)
+EGLSurface
+GLContextProviderEGL::CreateEGLSurface(void* aWindow)
+{
+    if (!sEGLLibrary.EnsureInitialized()) {
+        MOZ_CRASH("Failed to load EGL library!\n");
+    }
+
+    EGLConfig config;
+    if (!CreateConfig(&config)) {
+        MOZ_CRASH("Failed to create EGLConfig!\n");
+    }
+
+    MOZ_ASSERT(aWindow);
+
+    EGLSurface surface = sEGLLibrary.fCreateWindowSurface(EGL_DISPLAY(), config, aWindow, 0);
+
+    if (surface == EGL_NO_SURFACE) {
+        MOZ_CRASH("Failed to create EGLSurface!\n");
+    }
+
+    return surface;
+}
+
+void
+GLContextProviderEGL::DestroyEGLSurface(EGLSurface surface)
+{
+    if (!sEGLLibrary.EnsureInitialized()) {
+        MOZ_CRASH("Failed to load EGL library!\n");
+    }
+
+    sEGLLibrary.fDestroySurface(EGL_DISPLAY(), surface);
+}
+#endif // defined(ANDROID)
+
 already_AddRefed<GLContextEGL>
 GLContextEGL::CreateEGLPBufferOffscreenContext(const gfxIntSize& size)
 {
     EGLConfig config;
     EGLSurface surface;
 
     const EGLint numConfigs = 1; // We only need one.
     EGLConfig configs[numConfigs];
--- a/gfx/gl/GLContextProviderImpl.h
+++ b/gfx/gl/GLContextProviderImpl.h
@@ -5,16 +5,19 @@
 
 #ifndef IN_GL_CONTEXT_PROVIDER_H
 #error GLContextProviderImpl.h must only be included from GLContextProvider.h
 #endif
 
 #ifndef GL_CONTEXT_PROVIDER_NAME
 #error GL_CONTEXT_PROVIDER_NAME not defined
 #endif
+#if defined(ANDROID)
+typedef void* EGLSurface;
+#endif // defined(ANDROID)
 
 class GL_CONTEXT_PROVIDER_NAME
 {
 public:
     /**
      * Create a context that renders to the surface of the widget that is
      * passed in.  The context is always created with an RGB pixel format,
      * with no alpha, depth or stencil.  If any of those features are needed,
@@ -71,16 +74,21 @@ public:
      * @param aContext External context which will be wrapped by Gecko GLContext.
      * @param aSurface External surface which is used for external context.
      *
      * @return Wrapping Context to use for rendering
      */
     static already_AddRefed<GLContext>
     CreateWrappingExisting(void* aContext, void* aSurface);
 
+#if defined(ANDROID)
+    static EGLSurface CreateEGLSurface(void* aWindow);
+    static void DestroyEGLSurface(EGLSurface surface);
+#endif // defined(ANDROID)
+
     /**
      * Get a pointer to the global context, creating it if it doesn't exist.
      */
     static GLContext*
     GetGlobalContext();
 
     /**
      * Free any resources held by this Context Provider.
--- a/gfx/layers/composite/LayerManagerComposite.cpp
+++ b/gfx/layers/composite/LayerManagerComposite.cpp
@@ -49,16 +49,21 @@
 #include "nsDebug.h"                    // for NS_WARNING, NS_RUNTIMEABORT, etc
 #include "nsISupportsImpl.h"            // for Layer::AddRef, etc
 #include "nsIWidget.h"                  // for nsIWidget
 #include "nsPoint.h"                    // for nsIntPoint
 #include "nsRect.h"                     // for nsIntRect
 #include "nsRegion.h"                   // for nsIntRegion, etc
 #ifdef MOZ_WIDGET_ANDROID
 #include <android/log.h>
+#include "AndroidBridge.h"
+#include "opengl/CompositorOGL.h"
+#include "GLContextEGL.h"
+#include "GLContextProvider.h"
+#include "ScopedGLHelpers.h"
 #endif
 #include "GeckoProfiler.h"
 #include "TextRenderer.h"               // for TextRenderer
 
 class gfxContext;
 
 namespace mozilla {
 namespace layers {
@@ -301,16 +306,19 @@ LayerManagerComposite::EndTransaction(Dr
     // The results of our drawing always go directly into a pixel buffer,
     // so we don't need to pass any global transform here.
     mRoot->ComputeEffectiveTransforms(gfx::Matrix4x4());
 
     nsIntRegion opaque;
     ApplyOcclusionCulling(mRoot, opaque);
 
     Render();
+#ifdef MOZ_WIDGET_ANDROID
+    RenderToPresentationSurface();
+#endif
     mGeometryChanged = false;
   } else {
     // Modified layer tree
     mGeometryChanged = true;
   }
 
   mCompositor->ClearTargetContext();
   mTarget = nullptr;
@@ -763,16 +771,185 @@ LayerManagerComposite::Render()
     mCompositor->SetFBAcquireFence(mRoot);
   }
 
   mCompositor->GetWidget()->PostRender(this);
 
   RecordFrame();
 }
 
+#ifdef MOZ_WIDGET_ANDROID
+class ScopedCompositorProjMatrix {
+public:
+  ScopedCompositorProjMatrix(CompositorOGL* aCompositor, const Matrix4x4& aProjMatrix):
+    mCompositor(aCompositor),
+    mOriginalProjMatrix(mCompositor->GetProjMatrix())
+  {
+    mCompositor->SetProjMatrix(aProjMatrix);
+  }
+
+  ~ScopedCompositorProjMatrix()
+  {
+    mCompositor->SetProjMatrix(mOriginalProjMatrix);
+  }
+private:
+  CompositorOGL* const mCompositor;
+  const Matrix4x4 mOriginalProjMatrix;
+};
+
+class ScopedCompostitorSurfaceSize {
+public:
+  ScopedCompostitorSurfaceSize(CompositorOGL* aCompositor, const gfx::IntSize& aSize) :
+    mCompositor(aCompositor),
+    mOriginalSize(mCompositor->GetDestinationSurfaceSize())
+  {
+    mCompositor->SetDestinationSurfaceSize(aSize);
+  }
+  ~ScopedCompostitorSurfaceSize()
+  {
+    mCompositor->SetDestinationSurfaceSize(mOriginalSize);
+  }
+private:
+  CompositorOGL* const mCompositor;
+  const gfx::IntSize mOriginalSize;
+};
+
+class ScopedCompositorRenderOffset {
+public:
+  ScopedCompositorRenderOffset(CompositorOGL* aCompositor, const ScreenPoint& aOffset) :
+    mCompositor(aCompositor),
+    mOriginalOffset(mCompositor->GetScreenRenderOffset())
+  {
+    mCompositor->SetScreenRenderOffset(aOffset);
+  }
+  ~ScopedCompositorRenderOffset()
+  {
+    mCompositor->SetScreenRenderOffset(mOriginalOffset);
+  }
+private:
+  CompositorOGL* const mCompositor;
+  const ScreenPoint mOriginalOffset;
+};
+
+class ScopedContextSurfaceOverride {
+public:
+  ScopedContextSurfaceOverride(GLContextEGL* aContext, void* aSurface) :
+    mContext(aContext)
+  {
+    MOZ_ASSERT(aSurface);
+    mContext->SetEGLSurfaceOverride(aSurface);
+    mContext->MakeCurrent(true);
+  }
+  ~ScopedContextSurfaceOverride()
+  {
+    mContext->SetEGLSurfaceOverride(EGL_NO_SURFACE);
+    mContext->MakeCurrent(true);
+  }
+private:
+  GLContextEGL* const mContext;
+};
+
+void
+LayerManagerComposite::RenderToPresentationSurface()
+{
+  if (!AndroidBridge::Bridge()) {
+    return;
+  }
+
+  void* window = AndroidBridge::Bridge()->GetPresentationWindow();
+
+  if (!window) {
+    return;
+  }
+
+  EGLSurface surface = AndroidBridge::Bridge()->GetPresentationSurface();
+
+  if (!surface) {
+    //create surface;
+    surface = GLContextProviderEGL::CreateEGLSurface(window);
+    if (!surface) {
+      return;
+    }
+
+    AndroidBridge::Bridge()->SetPresentationSurface(surface);
+  }
+
+  CompositorOGL* compositor = static_cast<CompositorOGL*>(mCompositor.get());
+  GLContext* gl = compositor->gl();
+  GLContextEGL* egl = GLContextEGL::Cast(gl);
+
+  if (!egl) {
+    return;
+  }
+
+  const IntSize windowSize = AndroidBridge::Bridge()->GetNativeWindowSize(window);
+
+  if ((windowSize.width <= 0) || (windowSize.height <= 0)) {
+    return;
+  }
+
+  const int actualWidth = windowSize.width;
+  const int actualHeight = windowSize.height;
+
+  const gfx::IntSize originalSize = compositor->GetDestinationSurfaceSize();
+
+  const int pageWidth = originalSize.width;
+  const int pageHeight = originalSize.height;
+
+  float scale = 1.0;
+
+  if ((pageWidth > actualWidth) || (pageHeight > actualHeight)) {
+    const float scaleWidth = (float)actualWidth / (float)pageWidth;
+    const float scaleHeight = (float)actualHeight / (float)pageHeight;
+    scale = scaleWidth <= scaleHeight ? scaleWidth : scaleHeight;
+  }
+
+  const gfx::IntSize actualSize(actualWidth, actualHeight);
+  ScopedCompostitorSurfaceSize overrideSurfaceSize(compositor, actualSize);
+
+  const ScreenPoint offset((actualWidth - (int)(scale * pageWidth)) / 2, 0);
+  ScopedCompositorRenderOffset overrideRenderOffset(compositor, offset);
+  ScopedContextSurfaceOverride overrideSurface(egl, surface);
+
+  nsIntRegion invalid;
+  Rect bounds(0.0f, 0.0f, scale * pageWidth, (float)actualHeight);
+  Rect rect, actualBounds;
+
+  mCompositor->BeginFrame(invalid, nullptr, bounds, &rect, &actualBounds);
+
+  // Override the projection matrix since the presentation frame buffer
+  // is probably not the same size as the device frame buffer. The override
+  // projection matrix also scales the content to fit into the presentation
+  // frame buffer.
+  Matrix viewMatrix;
+  viewMatrix.PreTranslate(-1.0, 1.0);
+  viewMatrix.PreScale((2.0f * scale) / (float)actualWidth, (2.0f * scale) / (float)actualHeight);
+  viewMatrix.PreScale(1.0f, -1.0f);
+  viewMatrix.PreTranslate((int)((float)offset.x / scale), offset.y);
+
+  Matrix4x4 projMatrix = Matrix4x4::From2D(viewMatrix);
+
+  ScopedCompositorProjMatrix overrideProjMatrix(compositor, projMatrix);
+
+  // The Java side of Fennec sets a scissor rect that accounts for
+  // chrome such as the URL bar. Override that so that the entire frame buffer
+  // is cleared.
+  ScopedScissorRect screen(egl, 0, 0, actualWidth, actualHeight);
+  egl->fClearColor(0.0, 0.0, 0.0, 0.0);
+  egl->fClear(LOCAL_GL_COLOR_BUFFER_BIT);
+
+  const nsIntRect clipRect = nsIntRect(0, 0, (int)(scale * pageWidth), actualHeight);
+  RootLayer()->Prepare(RenderTargetPixel::FromUntyped(clipRect));
+  RootLayer()->RenderLayer(clipRect);
+
+  mCompositor->EndFrame();
+  mCompositor->SetFBAcquireFence(mRoot);
+}
+#endif
+
 static void
 SubtractTransformedRegion(nsIntRegion& aRegion,
                           const nsIntRegion& aRegionToSubtract,
                           const Matrix4x4& aTransform)
 {
   if (aRegionToSubtract.IsEmpty()) {
     return;
   }
--- a/gfx/layers/composite/LayerManagerComposite.h
+++ b/gfx/layers/composite/LayerManagerComposite.h
@@ -271,16 +271,19 @@ private:
                                              nsIntRegion& aScreenRegion,
                                              nsIntRegion& aLowPrecisionScreenRegion,
                                              const gfx::Matrix4x4& aTransform);
 
   /**
    * Render the current layer tree to the active target.
    */
   void Render();
+#ifdef MOZ_WIDGET_ANDROID
+  void RenderToPresentationSurface();
+#endif
 
   /**
    * Render debug overlays such as the FPS/FrameCounter above the frame.
    */
   void RenderDebugOverlay(const gfx::Rect& aBounds);
 
 
   RefPtr<CompositingRenderTarget> PushGroupForLayerEffects();
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -795,16 +795,32 @@ CompositorParent::SchedulePauseOnComposi
   MOZ_ASSERT(CompositorLoop());
   CompositorLoop()->PostTask(FROM_HERE, pauseTask);
 
   // Wait until the pause has actually been processed by the compositor thread
   lock.Wait();
 }
 
 bool
+CompositorParent::ScheduleResumeOnCompositorThread()
+{
+  MonitorAutoLock lock(mResumeCompositionMonitor);
+
+  CancelableTask *resumeTask =
+    NewRunnableMethod(this, &CompositorParent::ResumeComposition);
+  MOZ_ASSERT(CompositorLoop());
+  CompositorLoop()->PostTask(FROM_HERE, resumeTask);
+
+  // Wait until the resume has actually been processed by the compositor thread
+  lock.Wait();
+
+  return !mPaused;
+}
+
+bool
 CompositorParent::ScheduleResumeOnCompositorThread(int width, int height)
 {
   MonitorAutoLock lock(mResumeCompositionMonitor);
 
   CancelableTask *resumeTask =
     NewRunnableMethod(this, &CompositorParent::ResumeCompositionAndResize, width, height);
   MOZ_ASSERT(CompositorLoop());
   CompositorLoop()->PostTask(FROM_HERE, resumeTask);
--- a/gfx/layers/ipc/CompositorParent.h
+++ b/gfx/layers/ipc/CompositorParent.h
@@ -218,16 +218,17 @@ public:
 
   // Can be called from any thread
   void ScheduleRenderOnCompositorThread();
   void SchedulePauseOnCompositorThread();
   /**
    * Returns true if a surface was obtained and the resume succeeded; false
    * otherwise.
    */
+  bool ScheduleResumeOnCompositorThread();
   bool ScheduleResumeOnCompositorThread(int width, int height);
 
   virtual void ScheduleComposition();
   void NotifyShadowTreeTransaction(uint64_t aId, bool aIsFirstPaint,
       bool aScheduleComposite, uint32_t aPaintSequenceNumber,
       bool aIsRepeatTransaction);
 
   /**
--- a/gfx/layers/opengl/CompositorOGL.h
+++ b/gfx/layers/opengl/CompositorOGL.h
@@ -299,16 +299,29 @@ public:
    * Doing so lets us use gralloc the way it has been designed to be used
    * (see https://wiki.mozilla.org/Platform/GFX/Gralloc)
    */
   GLuint GetTemporaryTexture(GLenum aTarget, GLenum aUnit);
 
   const gfx::Matrix4x4& GetProjMatrix() const {
     return mProjMatrix;
   }
+
+  void SetProjMatrix(const gfx::Matrix4x4& aProjMatrix) {
+    mProjMatrix = aProjMatrix;
+  }
+
+  const gfx::IntSize GetDestinationSurfaceSize() const {
+    return gfx::IntSize (mSurfaceSize.width, mSurfaceSize.height);
+  }
+
+  const ScreenPoint& GetScreenRenderOffset() const {
+    return mRenderOffset;
+  }
+
 private:
   virtual gfx::IntSize GetWidgetSize() const override
   {
     return mWidgetSize;
   }
 
   bool InitializeVR();
   void DestroyVR(GLContext *gl);
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -289,16 +289,19 @@ public class GeckoAppShell
     // a composition, so there is no need to schedule a composition after
     // resuming.
     public static native void scheduleResumeComposition(int width, int height);
 
     public static native float computeRenderIntegrity();
 
     public static native SurfaceBits getSurfaceBits(Surface surface);
 
+    public static native void addPresentationSurface(Surface surface);
+    public static native void removePresentationSurface(Surface surface);
+
     public static native void onFullScreenPluginHidden(View view);
 
     public static class CreateShortcutFaviconLoadedListener implements OnFaviconLoadedListener {
         private final String title;
         private final String url;
 
         public CreateShortcutFaviconLoadedListener(final String url, final String title) {
             this.url = url;
--- a/mobile/android/base/MediaPlayerManager.java
+++ b/mobile/android/base/MediaPlayerManager.java
@@ -1,22 +1,31 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
+import android.app.Presentation;
+import android.content.Context;
 import android.os.Bundle;
 import android.support.v4.app.Fragment;
 import android.support.v7.media.MediaControlIntent;
 import android.support.v7.media.MediaRouteSelector;
 import android.support.v7.media.MediaRouter;
 import android.support.v7.media.MediaRouter.RouteInfo;
 import android.util.Log;
+import android.view.Display;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
 
 import com.google.android.gms.cast.CastMediaControlIntent;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.mozglue.JNITarget;
 import org.mozilla.gecko.util.EventCallback;
@@ -56,16 +65,17 @@ public class MediaPlayerManager extends 
     private static void debug(String msg) {
         if (SHOW_DEBUG) {
             Log.d(LOGTAG, msg);
         }
     }
 
     private MediaRouter mediaRouter = null;
     private final Map<String, GeckoMediaPlayer> displays = new HashMap<String, GeckoMediaPlayer>();
+    private GeckoPresentation presentation = null;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         EventDispatcher.getInstance().registerGeckoThreadListener(this,
                 "MediaPlayer:Load",
                 "MediaPlayer:Start",
                 "MediaPlayer:Stop",
@@ -130,47 +140,53 @@ public class MediaPlayerManager extends 
     private final MediaRouter.Callback callback =
         new MediaRouter.Callback() {
             @Override
             public void onRouteRemoved(MediaRouter router, RouteInfo route) {
                 debug("onRouteRemoved: route=" + route);
                 displays.remove(route.getId());
                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(
                         "MediaPlayer:Removed", route.getId()));
+                updatePresentation();
             }
 
             @SuppressWarnings("unused")
             public void onRouteSelected(MediaRouter router, int type, MediaRouter.RouteInfo route) {
+                updatePresentation();
             }
 
             // These methods aren't used by the support version Media Router
             @SuppressWarnings("unused")
             public void onRouteUnselected(MediaRouter router, int type, RouteInfo route) {
+                updatePresentation();
             }
 
             @Override
             public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo route) {
+                updatePresentation();
             }
 
             @Override
             public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) {
             }
 
             @Override
             public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo route) {
                 debug("onRouteAdded: route=" + route);
                 final GeckoMediaPlayer display = getMediaPlayerForRoute(route);
                 saveAndNotifyOfDisplay("MediaPlayer:Added", route, display);
+                updatePresentation();
             }
 
             @Override
             public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) {
                 debug("onRouteChanged: route=" + route);
                 final GeckoMediaPlayer display = displays.get(route.getId());
                 saveAndNotifyOfDisplay("MediaPlayer:Changed", route, display);
+                updatePresentation();
             }
 
             private void saveAndNotifyOfDisplay(final String eventName,
                     MediaRouter.RouteInfo route, final GeckoMediaPlayer display) {
                 if (display == null) {
                     return;
                 }
 
@@ -216,9 +232,85 @@ public class MediaPlayerManager extends 
         mediaRouter = MediaRouter.getInstance(getActivity());
         final MediaRouteSelector selectorBuilder = new MediaRouteSelector.Builder()
             .addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)
             .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
             .addControlCategory(CastMediaControlIntent.categoryForCast(ChromeCast.MIRROR_RECEIVER_APP_ID))
             .build();
         mediaRouter.addCallback(selectorBuilder, callback, MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
     }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        presentation.dismiss();
+        presentation = null;
+    }
+
+    private void updatePresentation() {
+        if (mediaRouter == null) {
+            return;
+        }
+
+        MediaRouter.RouteInfo route = mediaRouter.getSelectedRoute();
+        Display display = route != null ? route.getPresentationDisplay() : null;
+
+        if (display != null) {
+            if ((presentation != null) && (presentation.getDisplay() != display)) {
+                presentation.dismiss();
+                presentation = null;
+            }
+
+            if (presentation == null) {
+                presentation = new GeckoPresentation(getActivity(), display);
+
+                try {
+                    presentation.show();
+                } catch (WindowManager.InvalidDisplayException ex) {
+                    Log.w(LOGTAG, "Couldn't show presentation!  Display was removed in "
+                            + "the meantime.", ex);
+                    presentation = null;
+                }
+            }
+        } else if (presentation != null) {
+            presentation.dismiss();
+            presentation = null;
+        }
+    }
+
+    private static class SurfaceListener implements SurfaceHolder.Callback {
+        @Override
+        public void surfaceChanged(SurfaceHolder holder, int format, int width,
+                                                int height) {
+            // Surface changed so force a composite
+            GeckoAppShell.scheduleComposite();
+        }
+
+        @Override
+        public void surfaceCreated(SurfaceHolder holder) {
+            GeckoAppShell.addPresentationSurface(holder.getSurface());
+            GeckoAppShell.scheduleComposite();
+        }
+
+        @Override
+        public void surfaceDestroyed(SurfaceHolder holder) {
+            GeckoAppShell.removePresentationSurface(holder.getSurface());
+        }
+    }
+
+    private final static class GeckoPresentation extends Presentation {
+        private SurfaceView mView;
+        public GeckoPresentation(Context context, Display display) {
+            super(context, display);
+        }
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+
+            mView = new SurfaceView(getContext());
+            setContentView(mView, new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT));
+            mView.getHolder().addCallback(new SurfaceListener());
+        }
+    }
 }
--- a/mozglue/android/jni-stubs.inc
+++ b/mozglue/android/jni-stubs.inc
@@ -300,16 +300,54 @@ Java_org_mozilla_gecko_GeckoAppShell_get
 #endif
 
 #ifdef JNI_BINDINGS
   xul_dlsym("Java_org_mozilla_gecko_GeckoAppShell_getSurfaceBits", &f_Java_org_mozilla_gecko_GeckoAppShell_getSurfaceBits);
 #endif
 
 #ifdef JNI_STUBS
 
+typedef void (*Java_org_mozilla_gecko_GeckoAppShell_addPresentationSurface_t)(JNIEnv *, jclass, jobject);
+static Java_org_mozilla_gecko_GeckoAppShell_addPresentationSurface_t f_Java_org_mozilla_gecko_GeckoAppShell_addPresentationSurface;
+extern "C" NS_EXPORT void JNICALL
+Java_org_mozilla_gecko_GeckoAppShell_addPresentationSurface(JNIEnv * arg0, jclass arg1, jobject arg2) {
+    if (!f_Java_org_mozilla_gecko_GeckoAppShell_addPresentationSurface) {
+        arg0->ThrowNew(arg0->FindClass("java/lang/UnsupportedOperationException"),
+                       "JNI Function called before it was loaded");
+        return ;
+    }
+     f_Java_org_mozilla_gecko_GeckoAppShell_addPresentationSurface(arg0, arg1, arg2);
+}
+#endif
+
+#ifdef JNI_BINDINGS
+  xul_dlsym("Java_org_mozilla_gecko_GeckoAppShell_addPresentationSurface", &f_Java_org_mozilla_gecko_GeckoAppShell_addPresentationSurface);
+#endif
+
+#ifdef JNI_STUBS
+
+typedef void (*Java_org_mozilla_gecko_GeckoAppShell_removePresentationSurface_t)(JNIEnv *, jclass, jobject);
+static Java_org_mozilla_gecko_GeckoAppShell_removePresentationSurface_t f_Java_org_mozilla_gecko_GeckoAppShell_removePresentationSurface;
+extern "C" NS_EXPORT void JNICALL
+Java_org_mozilla_gecko_GeckoAppShell_removePresentationSurface(JNIEnv * arg0, jclass arg1, jobject arg2) {
+    if (!f_Java_org_mozilla_gecko_GeckoAppShell_removePresentationSurface) {
+        arg0->ThrowNew(arg0->FindClass("java/lang/UnsupportedOperationException"),
+                       "JNI Function called before it was loaded");
+        return ;
+    }
+     f_Java_org_mozilla_gecko_GeckoAppShell_removePresentationSurface(arg0, arg1, arg2);
+}
+#endif
+
+#ifdef JNI_BINDINGS
+  xul_dlsym("Java_org_mozilla_gecko_GeckoAppShell_removePresentationSurface", &f_Java_org_mozilla_gecko_GeckoAppShell_removePresentationSurface);
+#endif
+
+#ifdef JNI_STUBS
+
 typedef void (*Java_org_mozilla_gecko_GeckoAppShell_onFullScreenPluginHidden_t)(JNIEnv *, jclass, jobject);
 static Java_org_mozilla_gecko_GeckoAppShell_onFullScreenPluginHidden_t f_Java_org_mozilla_gecko_GeckoAppShell_onFullScreenPluginHidden;
 extern "C" NS_EXPORT void JNICALL
 Java_org_mozilla_gecko_GeckoAppShell_onFullScreenPluginHidden(JNIEnv * arg0, jclass arg1, jobject arg2) {
     if (!f_Java_org_mozilla_gecko_GeckoAppShell_onFullScreenPluginHidden) {
         arg0->ThrowNew(arg0->FindClass("java/lang/UnsupportedOperationException"),
                        "JNI Function called before it was loaded");
         return ;
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -39,16 +39,17 @@
 #include "nsPrintfCString.h"
 #include "NativeJSContainer.h"
 #include "nsContentUtils.h"
 #include "nsIScriptError.h"
 #include "nsIHttpChannel.h"
 
 #include "MediaCodec.h"
 #include "SurfaceTexture.h"
+#include "GLContextProvider.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::jni;
 using namespace mozilla::widget;
 
 AndroidBridge* AndroidBridge::sBridge = nullptr;
 pthread_t AndroidBridge::sJavaUiThread = -1;
@@ -816,16 +817,18 @@ AndroidBridge::OpenGraphicsLibraries()
         // Android 2.3+ (API level 9)
         handle = dlopen("libandroid.so", RTLD_LAZY | RTLD_LOCAL);
         if (handle) {
             ANativeWindow_fromSurface = (void* (*)(JNIEnv*, jobject))dlsym(handle, "ANativeWindow_fromSurface");
             ANativeWindow_release = (void (*)(void*))dlsym(handle, "ANativeWindow_release");
             ANativeWindow_setBuffersGeometry = (int (*)(void*, int, int, int)) dlsym(handle, "ANativeWindow_setBuffersGeometry");
             ANativeWindow_lock = (int (*)(void*, void*, void*)) dlsym(handle, "ANativeWindow_lock");
             ANativeWindow_unlockAndPost = (int (*)(void*))dlsym(handle, "ANativeWindow_unlockAndPost");
+            ANativeWindow_getWidth = (int (*)(void*))dlsym(handle, "ANativeWindow_getWidth");
+            ANativeWindow_getHeight = (int (*)(void*))dlsym(handle, "ANativeWindow_getHeight");
 
             // This is only available in Honeycomb and ICS. It was removed in Jelly Bean
             ANativeWindow_fromSurfaceTexture = (void* (*)(JNIEnv*, jobject))dlsym(handle, "ANativeWindow_fromSurfaceTexture");
 
             mHasNativeWindowAccess = ANativeWindow_fromSurface && ANativeWindow_release && ANativeWindow_lock && ANativeWindow_unlockAndPost;
 
             ALOG_BRIDGE("Successfully opened libandroid.so, have native window access? %d", mHasNativeWindowAccess);
         }
@@ -1270,16 +1273,26 @@ AndroidBridge::ReleaseNativeWindow(void 
 
     if (mHasNativeWindowAccess)
         ANativeWindow_release(window);
 
     // XXX: we don't ref the pointer we get from the fallback (GetNativeSurface), so we
     // have nothing to do here. We should probably ref it.
 }
 
+IntSize
+AndroidBridge::GetNativeWindowSize(void* window)
+{
+  if (!window || !ANativeWindow_getWidth || !ANativeWindow_getHeight) {
+    return IntSize(0, 0);
+  }
+
+  return IntSize(ANativeWindow_getWidth(window), ANativeWindow_getHeight(window));
+}
+
 void*
 AndroidBridge::AcquireNativeWindowFromSurfaceTexture(JNIEnv* aEnv, jobject aSurfaceTexture)
 {
     OpenGraphicsLibraries();
 
     if (mHasNativeWindowAccess && ANativeWindow_fromSurfaceTexture)
         return ANativeWindow_fromSurfaceTexture(aEnv, aSurfaceTexture);
 
@@ -1503,17 +1516,19 @@ void AndroidBridge::SyncFrameMetrics(con
     aFixedLayerMargins.bottom = viewTransform->FixedLayerMarginBottom();
     aFixedLayerMargins.left = viewTransform->FixedLayerMarginLeft();
 
     aOffset.x = viewTransform->OffsetX();
     aOffset.y = viewTransform->OffsetY();
 }
 
 AndroidBridge::AndroidBridge()
-  : mLayerClient(nullptr)
+  : mLayerClient(nullptr),
+    mPresentationWindow(nullptr),
+    mPresentationSurface(nullptr)
 {
 }
 
 AndroidBridge::~AndroidBridge()
 {
 }
 
 /* Implementation file */
@@ -2031,16 +2046,61 @@ AndroidBridge::RunDelayedUiThreadTasks()
         Task* task = nextTask->GetTask();
         delete nextTask;
 
         task->Run();
     }
     return -1;
 }
 
+void*
+AndroidBridge::GetPresentationWindow()
+{
+    return mPresentationWindow;
+}
+
+void
+AndroidBridge::SetPresentationWindow(void* aPresentationWindow)
+{
+     if (mPresentationWindow) {
+         const bool wasAlreadyPaused = nsWindow::IsCompositionPaused();
+         if (!wasAlreadyPaused) {
+             nsWindow::SchedulePauseComposition();
+         }
+
+         mPresentationWindow = aPresentationWindow;
+         if (mPresentationSurface) {
+             // destroy the egl surface!
+             // The compositor is paused so it should be okay to destroy
+             // the surface here.
+             mozilla::gl::GLContextProvider::DestroyEGLSurface(mPresentationSurface);
+             mPresentationSurface = nullptr;
+         }
+
+         if (!wasAlreadyPaused) {
+             nsWindow::ScheduleResumeComposition();
+         }
+     }
+     else {
+         mPresentationWindow = aPresentationWindow;
+     }
+}
+
+EGLSurface
+AndroidBridge::GetPresentationSurface()
+{
+    return mPresentationSurface;
+}
+
+void
+AndroidBridge::SetPresentationSurface(EGLSurface aPresentationSurface)
+{
+    mPresentationSurface = aPresentationSurface;
+}
+
 Object::LocalRef AndroidBridge::ChannelCreate(Object::Param stream) {
     JNIEnv* const env = GetJNIForThread();
     auto rv = Object::LocalRef::Adopt(env, env->CallStaticObjectMethod(
             sBridge->jReadableByteChannel, sBridge->jChannelCreate, stream.Get()));
     HandleUncaughtException(env);
     return rv;
 }
 
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -16,16 +16,17 @@
 
 #include "GeneratedJNIWrappers.h"
 #include "AndroidJavaWrappers.h"
 
 #include "nsIMutableArray.h"
 #include "nsIMIMEInfo.h"
 #include "nsColor.h"
 #include "gfxRect.h"
+#include "mozilla/gfx/Point.h"
 
 #include "nsIAndroidBridge.h"
 #include "nsIMobileMessageCallback.h"
 
 #include "mozilla/Likely.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Types.h"
@@ -259,16 +260,17 @@ public:
         WINDOW_FORMAT_RGBX_8888          = 2,
         WINDOW_FORMAT_RGB_565            = 4
     };
 
     bool HasNativeWindowAccess();
 
     void *AcquireNativeWindow(JNIEnv* aEnv, jobject aSurface);
     void ReleaseNativeWindow(void *window);
+    mozilla::gfx::IntSize GetNativeWindowSize(void* window);
 
     void *AcquireNativeWindowFromSurfaceTexture(JNIEnv* aEnv, jobject aSurface);
     void ReleaseNativeWindowForSurfaceTexture(void *window);
 
     bool LockWindow(void *window, unsigned char **bits, int *width, int *height, int *format, int *stride);
     bool UnlockWindow(void *window);
 
     void HandleGeckoMessage(JSContext* cx, JS::HandleObject message);
@@ -421,29 +423,40 @@ protected:
 
     void* (*ANativeWindow_fromSurface)(JNIEnv *env, jobject surface);
     void* (*ANativeWindow_fromSurfaceTexture)(JNIEnv *env, jobject surfaceTexture);
     void (*ANativeWindow_release)(void *window);
     int (*ANativeWindow_setBuffersGeometry)(void *window, int width, int height, int format);
 
     int (* ANativeWindow_lock)(void *window, void *outBuffer, void *inOutDirtyBounds);
     int (* ANativeWindow_unlockAndPost)(void *window);
+    int (* ANativeWindow_getWidth)(void * window);
+    int (* ANativeWindow_getHeight)(void * window);
 
     int (* Surface_lock)(void* surface, void* surfaceInfo, void* region, bool block);
     int (* Surface_unlockAndPost)(void* surface);
     void (* Region_constructor)(void* region);
     void (* Region_set)(void* region, void* rect);
 
 private:
     // This will always be accessed from one thread (the Java UI thread),
     // so we don't need to do locking to touch it.
     nsTArray<DelayedTask*> mDelayedTaskQueue;
 public:
     void PostTaskToUiThread(Task* aTask, int aDelayMs);
     int64_t RunDelayedUiThreadTasks();
+
+    void* GetPresentationWindow();
+    void SetPresentationWindow(void* aPresentationWindow);
+
+    EGLSurface GetPresentationSurface();
+    void SetPresentationSurface(EGLSurface aPresentationSurface);
+private:
+    void* mPresentationWindow;
+    EGLSurface mPresentationSurface;
 };
 
 class AutoJNIClass {
 private:
     JNIEnv* const mEnv;
     const jclass mClass;
 
 public:
--- a/widget/android/AndroidJNI.cpp
+++ b/widget/android/AndroidJNI.cpp
@@ -807,16 +807,37 @@ Java_org_mozilla_gecko_GeckoAppShell_get
 cleanup:
     AndroidBridge::Bridge()->UnlockWindow(window);
     AndroidBridge::Bridge()->ReleaseNativeWindow(window);
 
     return surfaceBits;
 }
 
 NS_EXPORT void JNICALL
+Java_org_mozilla_gecko_GeckoAppShell_addPresentationSurface(JNIEnv* jenv, jclass, jobject surface)
+{
+    if (surface != NULL) {
+        void* window = AndroidBridge::Bridge()->AcquireNativeWindow(jenv, surface);
+        if (window) {
+            AndroidBridge::Bridge()->SetPresentationWindow(window);
+        }
+    }
+}
+
+NS_EXPORT void JNICALL
+Java_org_mozilla_gecko_GeckoAppShell_removePresentationSurface(JNIEnv* jenv, jclass, jobject surface)
+{
+    void* window = AndroidBridge::Bridge()->GetPresentationWindow();
+    if (window) {
+        AndroidBridge::Bridge()->SetPresentationWindow(nullptr);
+        AndroidBridge::Bridge()->ReleaseNativeWindow(window);
+    }
+}
+
+NS_EXPORT void JNICALL
 Java_org_mozilla_gecko_GeckoAppShell_onFullScreenPluginHidden(JNIEnv* jenv, jclass, jobject view)
 {
   class ExitFullScreenRunnable : public nsRunnable {
     public:
       ExitFullScreenRunnable(jobject view) : mView(view) {}
 
       NS_IMETHODIMP Run() {
         JNIEnv* env = AndroidBridge::GetJNIEnv();
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -2417,16 +2417,39 @@ nsWindow::SetCompositor(mozilla::layers:
 void
 nsWindow::ScheduleComposite()
 {
     if (sCompositorParent) {
         sCompositorParent->ScheduleRenderOnCompositorThread();
     }
 }
 
+bool
+nsWindow::IsCompositionPaused()
+{
+    return sCompositorPaused;
+}
+
+void
+nsWindow::SchedulePauseComposition()
+{
+    if (sCompositorParent) {
+        sCompositorParent->SchedulePauseOnCompositorThread();
+        sCompositorPaused = true;
+    }
+}
+
+void
+nsWindow::ScheduleResumeComposition()
+{
+    if (sCompositorParent && sCompositorParent->ScheduleResumeOnCompositorThread()) {
+        sCompositorPaused = false;
+    }
+}
+
 void
 nsWindow::ScheduleResumeComposition(int width, int height)
 {
     if (sCompositorParent && sCompositorParent->ScheduleResumeOnCompositorThread(width, height)) {
         sCompositorPaused = false;
     }
 }
 
--- a/widget/android/nsWindow.h
+++ b/widget/android/nsWindow.h
@@ -150,17 +150,20 @@ public:
     virtual void DrawWindowUnderlay(LayerManagerComposite* aManager, nsIntRect aRect);
     virtual void DrawWindowOverlay(LayerManagerComposite* aManager, nsIntRect aRect);
 
     virtual mozilla::layers::CompositorParent* NewCompositorParent(int aSurfaceWidth, int aSurfaceHeight) override;
 
     static void SetCompositor(mozilla::layers::LayerManager* aLayerManager,
                               mozilla::layers::CompositorParent* aCompositorParent,
                               mozilla::layers::CompositorChild* aCompositorChild);
+    static bool IsCompositionPaused();
     static void ScheduleComposite();
+    static void SchedulePauseComposition();
+    static void ScheduleResumeComposition();
     static void ScheduleResumeComposition(int width, int height);
     static void ForceIsFirstPaint();
     static float ComputeRenderIntegrity();
     static mozilla::layers::APZCTreeManager* GetAPZCTreeManager();
     /* RootLayerTreeId() can only be called when GetAPZCTreeManager() returns non-null */
     static uint64_t RootLayerTreeId();
 
     virtual bool WidgetPaintsBackground();