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 id28570
push usercbook@mozilla.com
push dateMon, 13 Apr 2015 09:57:20 +0000
treeherdermozilla-central@1d857f4049d4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssnorp, jgilbert
bugs1148149
milestone40.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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();