Bug 759747 - Fix up fullscreen Flash handling on Android 4.0+ r=blassey a=akeybl MOBILE140_2012061216_RELBRANCH
authorJames Willcox <jwillcox@mozilla.com>
Wed, 06 Jun 2012 22:39:36 -0400
branchMOBILE140_2012061216_RELBRANCH
changeset 96072 b0f32cd82c9787cf3dda6d36a843011429b5b3b4
parent 96064 27b4239d954f790ed9cf15dfea71e9fe6288fb96
child 96076 064e784532d5b48c61f9ef124ed88a9df8e31c32
push id971
push userblassey@mozilla.com
push dateThu, 14 Jun 2012 01:39:32 +0000
treeherdermozilla-beta@b0f32cd82c97 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersblassey, akeybl
bugs759747
milestone14.0
Bug 759747 - Fix up fullscreen Flash handling on Android 4.0+ r=blassey a=akeybl
dom/plugins/base/nsNPAPIPluginInstance.cpp
dom/plugins/base/nsNPAPIPluginInstance.h
dom/plugins/base/nsPluginInstanceOwner.cpp
mobile/android/base/GeckoApp.java
mobile/android/base/GeckoAppShell.java
widget/android/AndroidBridge.cpp
widget/android/AndroidBridge.h
--- a/dom/plugins/base/nsNPAPIPluginInstance.cpp
+++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp
@@ -112,16 +112,17 @@ NS_IMPL_THREADSAFE_ISUPPORTS0(nsNPAPIPlu
 nsNPAPIPluginInstance::nsNPAPIPluginInstance()
   :
     mDrawingModel(kDefaultDrawingModel),
 #ifdef MOZ_WIDGET_ANDROID
     mSurface(nsnull),
     mANPDrawingModel(0),
     mOnScreen(true),
     mFullScreenOrientation(dom::eScreenOrientation_LandscapePrimary),
+    mFullScreen(false),
 #endif
     mRunning(NOT_STARTED),
     mWindowless(false),
     mTransparent(false),
     mCached(false),
     mUsesDOMForCursor(false),
     mInPluginInitCall(false),
     mPlugin(nsnull),
@@ -784,20 +785,25 @@ void nsNPAPIPluginInstance::MemoryPressu
 
   SendLifecycleEvent(this, kFreeMemory_ANPLifecycleAction);
 }
 
 void nsNPAPIPluginInstance::NotifyFullScreen(bool aFullScreen)
 {
   PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsNPAPIPluginInstance::NotifyFullScreen this=%p\n",this));
 
-  if (RUNNING != mRunning)
+  if (RUNNING != mRunning || mFullScreen == aFullScreen)
     return;
 
-  SendLifecycleEvent(this, aFullScreen ? kEnterFullScreen_ANPLifecycleAction : kExitFullScreen_ANPLifecycleAction);
+  mFullScreen = aFullScreen;
+  SendLifecycleEvent(this, mFullScreen ? kEnterFullScreen_ANPLifecycleAction : kExitFullScreen_ANPLifecycleAction);
+
+  if (mFullScreen && mFullScreenOrientation != dom::eScreenOrientation_None) {
+    AndroidBridge::Bridge()->LockScreenOrientation(mFullScreenOrientation);
+  }
 }
 
 void nsNPAPIPluginInstance::SetANPDrawingModel(PRUint32 aModel)
 {
   mANPDrawingModel = aModel;
 }
 
 void* nsNPAPIPluginInstance::GetJavaSurface()
@@ -813,16 +819,37 @@ void* nsNPAPIPluginInstance::GetJavaSurf
 void nsNPAPIPluginInstance::PostEvent(void* event)
 {
   PluginEventRunnable *r = new PluginEventRunnable(this, (ANPEvent*)event);
   mPostedEvents.AppendElement(nsRefPtr<PluginEventRunnable>(r));
 
   NS_DispatchToMainThread(r);
 }
 
+void nsNPAPIPluginInstance::SetFullScreenOrientation(PRUint32 orientation)
+{
+  if (mFullScreenOrientation == orientation)
+    return;
+
+  PRUint32 oldOrientation = mFullScreenOrientation;
+  mFullScreenOrientation = orientation;
+
+  if (mFullScreen) {
+    // We're already fullscreen so immediately apply the orientation change
+
+    if (mFullScreenOrientation != dom::eScreenOrientation_None) {
+      AndroidBridge::Bridge()->LockScreenOrientation(mFullScreenOrientation);
+    } else if (oldOrientation != dom::eScreenOrientation_None) {
+      // We applied an orientation when we entered fullscreen, but
+      // we don't want it anymore
+      AndroidBridge::Bridge()->UnlockScreenOrientation();
+    }
+  }
+}
+
 void nsNPAPIPluginInstance::PopPostedEvent(PluginEventRunnable* r)
 {
   mPostedEvents.RemoveElement(r);
 }
 
 #endif
 
 nsresult nsNPAPIPluginInstance::GetDrawingModel(PRInt32* aModel)
--- a/dom/plugins/base/nsNPAPIPluginInstance.h
+++ b/dom/plugins/base/nsNPAPIPluginInstance.h
@@ -164,24 +164,24 @@ public:
   bool IsOnScreen() {
     return mOnScreen;
   }
 
   PRUint32 GetANPDrawingModel() { return mANPDrawingModel; }
   void SetANPDrawingModel(PRUint32 aModel);
 
   void* GetJavaSurface();
+  void PostEvent(void* event);
 
-  void PostEvent(void* event);
   // These are really mozilla::dom::ScreenOrientation, but it's
   // difficult to include that here
   PRUint32 FullScreenOrientation() { return mFullScreenOrientation; }
-  void SetFullScreenOrientation(PRUint32 orientation) { mFullScreenOrientation  = orientation; }
+  void SetFullScreenOrientation(PRUint32 orientation);
+
 #endif
-
   nsresult NewStreamListener(const char* aURL, void* notifyData,
                              nsIPluginStreamListener** listener);
 
   nsNPAPIPluginInstance();
   virtual ~nsNPAPIPluginInstance();
 
   // To be called when an instance becomes orphaned, when
   // it's plugin is no longer guaranteed to be around.
@@ -256,16 +256,17 @@ protected:
   nsCOMPtr<nsIRunnable> mSurfaceGetter;
 
   friend class PluginEventRunnable;
 
   nsTArray<nsCOMPtr<PluginEventRunnable>> mPostedEvents;
   void PopPostedEvent(PluginEventRunnable* r);
 
   PRUint32 mFullScreenOrientation;
+  bool mFullScreen;
 #endif
 
   enum {
     NOT_STARTED,
     RUNNING,
     DESTROYING,
     DESTROYED
   } mRunning;
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp
+++ b/dom/plugins/base/nsPluginInstanceOwner.cpp
@@ -1778,17 +1778,17 @@ bool nsPluginInstanceOwner::AddPluginVie
   
     if (!mJavaView)
       return false;
 
     mJavaView = (void*)AndroidBridge::GetJNIEnv()->NewGlobalRef((jobject)mJavaView);
   }
 
   if (AndroidBridge::Bridge())
-    AndroidBridge::Bridge()->AddPluginView((jobject)mJavaView, aRect, mFullScreen, mInstance->FullScreenOrientation());
+    AndroidBridge::Bridge()->AddPluginView((jobject)mJavaView, aRect, mFullScreen);
 
   if (mFullScreen)
     sFullScreenInstance = this;
 
   return true;
 }
 
 void nsPluginInstanceOwner::RemovePluginView()
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -137,16 +137,17 @@ abstract public class GeckoApp
     public static FormAssistPopup mFormAssistPopup;
     public Favicons mFavicons;
 
     private static LayerController mLayerController;
     private static GeckoLayerClient mLayerClient;
     private AboutHomeContent mAboutHomeContent;
     private static AbsoluteLayout mPluginContainer;
 
+    private FullScreenHolder mFullScreenPluginContainer;
     private View mFullScreenPluginView;
 
     private int mRestoreMode = GeckoAppShell.RESTORE_NONE;
     private boolean mInitialized = false;
 
     static class ExtraMenuItem implements MenuItem.OnMenuItemClickListener {
         String label;
         String icon;
@@ -1354,45 +1355,52 @@ abstract public class GeckoApp
     }
 
     void handleWindowClose(final int tabId) {
         Tabs tabs = Tabs.getInstance();
         Tab tab = tabs.getTab(tabId);
         tabs.closeTab(tab);
     }
 
-    private void addFullScreenPluginView(View view, int orientation) {
+    private void addFullScreenPluginView(View view) {
         if (mFullScreenPluginView != null) {
             Log.w(LOGTAG, "Already have a fullscreen plugin view");
             return;
         }
 
         setFullScreen(true);
-        mBrowserToolbar.hide();
-
-        if (orientation != GeckoScreenOrientationListener.eScreenOrientation_None)
-            GeckoScreenOrientationListener.getInstance().lockScreenOrientation(orientation);
 
         view.setWillNotDraw(false);
         if (view instanceof SurfaceView) {
             ((SurfaceView) view).setZOrderOnTop(true);
         }
 
-        mPluginContainer.addView(view, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+        mFullScreenPluginContainer = new FullScreenHolder(this);
+
+        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
+                            ViewGroup.LayoutParams.MATCH_PARENT,
+                            ViewGroup.LayoutParams.MATCH_PARENT,
+                            Gravity.CENTER);
+        mFullScreenPluginContainer.addView(view, layoutParams);
+
+
+        FrameLayout decor = (FrameLayout)getWindow().getDecorView();
+        decor.addView(mFullScreenPluginContainer, layoutParams);
+
         mFullScreenPluginView = view;
     }
 
-    void addPluginView(final View view, final Rect rect, final boolean isFullScreen, final int orientation) {
+    void addPluginView(final View view, final Rect rect, final boolean isFullScreen) {
         mMainHandler.post(new Runnable() { 
             public void run() {
                 Tabs tabs = Tabs.getInstance();
                 Tab tab = tabs.getSelectedTab();
 
                 if (isFullScreen) {
-                    addFullScreenPluginView(view, orientation);
+                    addFullScreenPluginView(view);
                     return;
                 }
 
                 PluginLayer layer = (PluginLayer) tab.getPluginLayer(view);
                 if (layer == null) {
                     layer = new PluginLayer(view, rect, mLayerController.getView().getRenderer().getMaxTextureSize());
                     tab.addPluginLayer(view, layer);
                 } else {
@@ -1411,23 +1419,33 @@ abstract public class GeckoApp
             return;
         }
 
         if (mFullScreenPluginView != view) {
             Log.w(LOGTAG, "Passed view is not the current full screen view");
             return;
         }
 
+        mFullScreenPluginContainer.removeView(mFullScreenPluginView);
+
+        // We need do do this on the next iteration in order to avoid
+        // a deadlock, see comment below in FullScreenHolder
+        mMainHandler.post(new Runnable() { 
+            public void run() {
+                mLayerController.getView().setVisibility(View.VISIBLE);
+            }
+        });
+
+        FrameLayout decor = (FrameLayout)getWindow().getDecorView();
+        decor.removeView(mFullScreenPluginContainer);
+        
+        mFullScreenPluginView = null;
+
         GeckoScreenOrientationListener.getInstance().unlockScreenOrientation();
-
         setFullScreen(false);
-        mBrowserToolbar.show();
-        
-        mPluginContainer.removeView(view);
-        mFullScreenPluginView = null;
     }
 
     void removePluginView(final View view, final boolean isFullScreen) {
         mMainHandler.post(new Runnable() { 
             public void run() {
                 Tabs tabs = Tabs.getInstance();
                 Tab tab = tabs.getSelectedTab();
 
@@ -2929,9 +2947,73 @@ abstract public class GeckoApp
             public boolean onTouch(View view, MotionEvent event) {
                 if (event == null)
                     return true;
                 GeckoAppShell.sendEventToGecko(GeckoEvent.createMotionEvent(event));
                 return true;
             }
         });
     }
+
+
+    private class FullScreenHolder extends FrameLayout {
+
+        public FullScreenHolder(Context ctx) {
+            super(ctx);
+        }
+
+        @Override
+        public void addView(View view, int index) {
+            /**
+             * This normally gets called when Flash adds a separate SurfaceView
+             * for the video. It is unhappy if we have the LayerView underneath
+             * it for some reason so we need to hide that. Hiding the LayerView causes
+             * its surface to be destroyed, which causes a pause composition
+             * event to be sent to Gecko. We synchronously wait for that to be
+             * processed. Simultaneously, however, Flash is waiting on a mutex so
+             * the post() below is an attempt to avoid a deadlock.
+             */
+            super.addView(view, index);
+
+            mMainHandler.post(new Runnable() { 
+                public void run() {
+                    mLayerController.getView().setVisibility(View.INVISIBLE);
+                }
+            });
+        }
+
+        /**
+         * The methods below are simply copied from what Android WebKit does.
+         * It wasn't ever called in my testing, but might as well
+         * keep it in case it is for some reason. The methods
+         * all return true because we don't want any events
+         * leaking out from the fullscreen view.
+         */
+        @Override
+        public boolean onKeyDown(int keyCode, KeyEvent event) {
+            if (event.isSystem()) {
+                return super.onKeyDown(keyCode, event);
+            }
+            mFullScreenPluginView.onKeyDown(keyCode, event);
+            return true;
+        }
+
+        @Override
+        public boolean onKeyUp(int keyCode, KeyEvent event) {
+            if (event.isSystem()) {
+                return super.onKeyUp(keyCode, event);
+            }
+            mFullScreenPluginView.onKeyUp(keyCode, event);
+            return true;
+        }
+
+        @Override
+        public boolean onTouchEvent(MotionEvent event) {
+            return true;
+        }
+
+        @Override
+        public boolean onTrackballEvent(MotionEvent event) {
+            mFullScreenPluginView.onTrackballEvent(event);
+            return true;
+        }
+    }
 }
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -1527,23 +1527,23 @@ public class GeckoAppShell
         catch (Exception e) {
             return true;
         }
     }
 
     public static void addPluginView(View view,
                                      int x, int y,
                                      int w, int h,
-                                     boolean isFullScreen, int orientation)
+                                     boolean isFullScreen)
 {
         ImmutableViewportMetrics pluginViewport;
 
-        Log.i(LOGTAG, "addPluginView:" + view + " @ x:" + x + " y:" + y + " w:" + w + " h:" + h + "fullscreen: " + isFullScreen + " orientation: " + orientation);
+        Log.i(LOGTAG, "addPluginView:" + view + " @ x:" + x + " y:" + y + " w:" + w + " h:" + h + " fullscreen: " + isFullScreen);
         
-        GeckoApp.mAppContext.addPluginView(view, new Rect(x, y, x + w, y + h), isFullScreen, orientation);
+        GeckoApp.mAppContext.addPluginView(view, new Rect(x, y, x + w, y + h), isFullScreen);
     }
 
     public static void removePluginView(View view, boolean isFullScreen) {
         Log.i(LOGTAG, "removePluginView:" + view + " fullscreen: " + isFullScreen);
         GeckoApp.mAppContext.removePluginView(view, isFullScreen);
     }
 
     public static Surface createSurface() {
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -213,17 +213,17 @@ AndroidBridge::Init(JNIEnv *jEnv,
     if (apiVersion <= 8 /* Froyo */)
         jSurfacePointerField = jEnv->GetFieldID(jSurfaceClass, "mSurface", "I");
     else /* not Froyo */
         jSurfacePointerField = jEnv->GetFieldID(jSurfaceClass, "mNativeSurface", "I");
 
 #ifdef MOZ_JAVA_COMPOSITOR
     jPumpMessageLoop = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "pumpMessageLoop", "()V");
 
-    jAddPluginView = jEnv->GetStaticMethodID(jGeckoAppShellClass, "addPluginView", "(Landroid/view/View;IIIIZI)V");
+    jAddPluginView = jEnv->GetStaticMethodID(jGeckoAppShellClass, "addPluginView", "(Landroid/view/View;IIIIZ)V");
     jRemovePluginView = jEnv->GetStaticMethodID(jGeckoAppShellClass, "removePluginView", "(Landroid/view/View;Z)V");
 
     jCreateSurface = jEnv->GetStaticMethodID(jGeckoAppShellClass, "createSurface", "()Landroid/view/Surface;");
     jShowSurface = jEnv->GetStaticMethodID(jGeckoAppShellClass, "showSurface", "(Landroid/view/Surface;IIIIZZ)V");
     jHideSurface = jEnv->GetStaticMethodID(jGeckoAppShellClass, "hideSurface", "(Landroid/view/Surface;)V");
     jDestroySurface = jEnv->GetStaticMethodID(jGeckoAppShellClass, "destroySurface", "(Landroid/view/Surface;)V");
 
     jLayerView = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("org/mozilla/gecko/gfx/LayerView"));
@@ -2341,28 +2341,28 @@ NS_IMETHODIMP nsAndroidBridge::GetBrowse
 NS_IMETHODIMP nsAndroidBridge::SetBrowserApp(nsIAndroidBrowserApp *aBrowserApp)
 {
     if (nsAppShell::gAppShell)
         nsAppShell::gAppShell->SetBrowserApp(aBrowserApp);
     return NS_OK;
 }
 
 void
-AndroidBridge::AddPluginView(jobject view, const gfxRect& rect, bool isFullScreen, int orientation) {
+AndroidBridge::AddPluginView(jobject view, const gfxRect& rect, bool isFullScreen) {
     JNIEnv *env = GetJNIEnv();
     if (!env)
         return;
 
     AutoLocalJNIFrame jniFrame(env);
 
 #if MOZ_JAVA_COMPOSITOR
     env->CallStaticVoidMethod(sBridge->mGeckoAppShellClass,
                               sBridge->jAddPluginView, view,
                               (int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height,
-                              isFullScreen, orientation);
+                              isFullScreen);
 #else
     env->CallStaticVoidMethod(sBridge->mGeckoAppShellClass,
                               sBridge->jAddPluginView, view,
                               rect.x, rect.y, rect.width, rect.height);
 #endif
 }
 
 void
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -368,17 +368,17 @@ public:
     void SyncViewportInfo(const nsIntRect& aDisplayPort, float aDisplayResolution, bool aLayersUpdated,
                           nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY);
 
     jobject CreateSurface();
     void DestroySurface(jobject surface);
     void ShowSurface(jobject surface, const gfxRect& aRect, bool aInverted, bool aBlend);
     void HideSurface(jobject surface);
 
-    void AddPluginView(jobject view, const gfxRect& rect, bool isFullScreen, int orientation);
+    void AddPluginView(jobject view, const gfxRect& rect, bool isFullScreen);
     void RemovePluginView(jobject view, bool isFullScreen);
 
     // This method doesn't take a ScreenOrientation because it's an enum and
     // that would require including the header which requires include IPC
     // headers which requires including basictypes.h which requires a lot of
     // changes...
     void GetScreenOrientation(dom::ScreenOrientationWrapper& aOrientation);
     void EnableScreenOrientationNotifications();