Bug 1321638 - Part 3: Add ScreenOrientationDelegate. r=jchen draft
authorNick Alexander <nalexander@mozilla.com>
Fri, 16 Dec 2016 15:45:27 -0800
changeset 450553 4817140c7142abbcb589495ce2d3c169ca0bd9df
parent 450552 33d28838ec807e7f6bf589245e755eb9c451d94e
child 450554 4c82e7d186a7b4e93e826f60e7e7ea8af6e851cf
push id38896
push usernalexander@mozilla.com
push dateFri, 16 Dec 2016 23:56:04 +0000
reviewersjchen
bugs1321638
milestone53.0a1
Bug 1321638 - Part 3: Add ScreenOrientationDelegate. r=jchen This patch abstracts setting the screen orientation into an interface (but not into the already overloaded GeckoInterface). I feel this form obscures the (eventual) connection between the GeckoView widget and its embedding Activity, but it removes the explicit getActivity() and it's certainly simpler than previous iterations. MozReview-Commit-ID: 8a8bPTlcp3T
mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
mobile/android/base/moz.build
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoScreenOrientation.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/ScreenOrientationDelegate.java
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -128,16 +128,17 @@ import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
 public abstract class GeckoApp
     extends GeckoActivity
     implements
     BundleEventListener,
     ContextGetter,
     GeckoAppShell.GeckoInterface,
+    ScreenOrientationDelegate,
     GeckoMenu.Callback,
     GeckoMenu.MenuPresenter,
     Tabs.OnTabsChangedListener,
     ViewTreeObserver.OnGlobalLayoutListener {
 
     private static final String LOGTAG = "GeckoApp";
     private static final long ONE_DAY_MS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.DAYS);
 
@@ -1124,16 +1125,17 @@ public abstract class GeckoApp
         // GeckoAppShell is tightly coupled to us, rather than
         // the app context, because various parts of Fennec (e.g.,
         // GeckoScreenOrientation) use GAS to access the Activity in
         // the guise of fetching a Context.
         // When that's fixed, `this` can change to
         // `(GeckoApplication) getApplication()` here.
         GeckoAppShell.setContextGetter(this);
         GeckoAppShell.setGeckoInterface(this);
+        GeckoAppShell.setScreenOrientationDelegate(this);
 
         // Tell Stumbler to register a local broadcast listener to listen for preference intents.
         // We do this via intents since we can't easily access Stumbler directly,
         // as it might be compiled outside of Fennec.
         getApplicationContext().sendBroadcast(
                 new Intent(INTENT_REGISTER_STUMBLER_LISTENER)
         );
 
@@ -2075,16 +2077,17 @@ public abstract class GeckoApp
 
         if (mIsAbortingAppLaunch) {
             return;
         }
 
         foregrounded = true;
 
         GeckoAppShell.setGeckoInterface(this);
+        GeckoAppShell.setScreenOrientationDelegate(this);
 
         if (lastSelectedTabId >= 0 && (lastActiveGeckoApp == null || lastActiveGeckoApp.get() != this)) {
             Tabs.getInstance().selectTab(lastSelectedTabId);
         }
 
         int newOrientation = getResources().getConfiguration().orientation;
         if (GeckoScreenOrientation.getInstance().update(newOrientation)) {
             refreshChrome();
@@ -2858,9 +2861,20 @@ public abstract class GeckoApp
     public String getDefaultChromeURI() {
         // Use the chrome URI specified by Gecko's defaultChromeURI pref.
         return null;
     }
 
     public GeckoView getGeckoView() {
         return mLayerView;
     }
+
+    @Override
+    public boolean setRequestedOrientationForCurrentActivity(int requestedActivityInfoOrientation) {
+        // We want to support the Screen Orientation API, and it always makes sense to lock the
+        // orientation of a browser Activity, so we support locking.
+        if (getRequestedOrientation() == requestedActivityInfoOrientation) {
+            return false;
+        }
+        setRequestedOrientation(requestedActivityInfoOrientation);
+        return true;
+    }
 }
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -275,16 +275,17 @@ gvjar.sources += [geckoview_source_dir +
     'NSSBridge.java',
     'permissions/PermissionBlock.java',
     'permissions/Permissions.java',
     'permissions/PermissionsHelper.java',
     'PrefsHelper.java',
     'process/GeckoProcessManager.java',
     'process/GeckoServiceChildProcess.java',
     'ScreenManagerHelper.java',
+    'ScreenOrientationDelegate.java',
     'sqlite/ByteBufferInputStream.java',
     'sqlite/MatrixBlobCursor.java',
     'sqlite/SQLiteBridge.java',
     'sqlite/SQLiteBridgeException.java',
     'TouchEventInterceptor.java',
 ]]
 
 gvjar.sources += [geckoview_thirdparty_source_dir + f for f in [
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
@@ -469,17 +469,17 @@ public class GeckoAppShell
                                                      float w, int accuracy, long time);
 
     @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
     /* package */ static native void onLocationChanged(double latitude, double longitude,
                                                        double altitude, float accuracy,
                                                        float bearing, float speed, long time);
 
     private static class DefaultListeners
-            implements SensorEventListener, LocationListener, NotificationListener {
+            implements SensorEventListener, LocationListener, NotificationListener, ScreenOrientationDelegate {
         @Override
         public void onAccuracyChanged(Sensor sensor, int accuracy) {
         }
 
         private static int HalSensorAccuracyFor(int androidAccuracy) {
             switch (androidAccuracy) {
             case SensorManager.SENSOR_STATUS_UNRELIABLE:
                 return GeckoHalDefines.SENSOR_ACCURACY_UNRELIABLE;
@@ -599,23 +599,34 @@ public class GeckoAppShell
             // Default is to not show the notification, and immediate send close message.
             GeckoAppShell.onNotificationClose(name, cookie);
         }
 
         @Override // NotificationListener
         public void closeNotification(String name) {
             // Do nothing.
         }
+
+        @Override
+        public boolean setRequestedOrientationForCurrentActivity(int requestedActivityInfoOrientation) {
+            // Do nothing, and report that the orientation was not set.
+            return false;
+        }
     }
 
     private static final DefaultListeners DEFAULT_LISTENERS = new DefaultListeners();
     private static SensorEventListener sSensorListener = DEFAULT_LISTENERS;
     private static LocationListener sLocationListener = DEFAULT_LISTENERS;
     private static NotificationListener sNotificationListener = DEFAULT_LISTENERS;
 
+    /**
+     * A delegate for supporting the Screen Orientation API.
+     */
+    private static ScreenOrientationDelegate sScreenOrientationDelegate = DEFAULT_LISTENERS;
+
     public static SensorEventListener getSensorListener() {
         return sSensorListener;
     }
 
     public static void setSensorListener(final SensorEventListener listener) {
         sSensorListener = (listener != null) ? listener : DEFAULT_LISTENERS;
     }
 
@@ -630,16 +641,24 @@ public class GeckoAppShell
     public static NotificationListener getNotificationListener() {
         return sNotificationListener;
     }
 
     public static void setNotificationListener(final NotificationListener listener) {
         sNotificationListener = (listener != null) ? listener : DEFAULT_LISTENERS;
     }
 
+    public static ScreenOrientationDelegate getScreenOrientationDelegate() {
+        return sScreenOrientationDelegate;
+    }
+
+    public static void setScreenOrientationDelegate(ScreenOrientationDelegate screenOrientationDelegate) {
+        sScreenOrientationDelegate = (screenOrientationDelegate != null) ? screenOrientationDelegate : DEFAULT_LISTENERS;
+    }
+
     @WrapForJNI(calledFrom = "gecko")
     private static void enableSensor(int aSensortype) {
         GeckoInterface gi = getGeckoInterface();
         if (gi == null) {
             return;
         }
         SensorManager sm = (SensorManager)
             getApplicationContext().getSystemService(Context.SENSOR_SERVICE);
@@ -2067,21 +2086,23 @@ public class GeckoAppShell
 
     @WrapForJNI(calledFrom = "gecko")
     private static void disableScreenOrientationNotifications() {
         GeckoScreenOrientation.getInstance().disableNotifications();
     }
 
     @WrapForJNI(calledFrom = "gecko")
     private static void lockScreenOrientation(int aOrientation) {
+        // TODO: don't vector through GeckoAppShell.
         GeckoScreenOrientation.getInstance().lock(aOrientation);
     }
 
     @WrapForJNI(calledFrom = "gecko")
     private static void unlockScreenOrientation() {
+        // TODO: don't vector through GeckoAppShell.
         GeckoScreenOrientation.getInstance().unlock();
     }
 
     @WrapForJNI(calledFrom = "gecko")
     private static void notifyWakeLockChanged(String topic, String state) {
         if (getGeckoInterface() != null)
             getGeckoInterface().notifyWakeLockChanged(topic, state);
     }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoScreenOrientation.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoScreenOrientation.java
@@ -1,22 +1,23 @@
 /* -*- 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 org.mozilla.gecko.annotation.WrapForJNI;
-
+import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.util.Log;
 import android.view.Surface;
-import android.app.Activity;
+import android.view.WindowManager;
+
+import org.mozilla.gecko.annotation.WrapForJNI;
 
 import java.util.Arrays;
 import java.util.List;
 
 /*
  * Updates, locks and unlocks the screen orientation.
  *
  * Note: Replaces the OnOrientationChangeListener to avoid redundant rotation
@@ -87,21 +88,21 @@ public class GeckoScreenOrientation {
 
     /*
      * Update screen orientation.
      * Retrieve orientation and rotation via GeckoAppShell.
      *
      * @return Whether the screen orientation has changed.
      */
     public boolean update() {
-        Activity activity = getActivity();
-        if (activity == null) {
+        final Context appContext = GeckoAppShell.getApplicationContext();
+        if (appContext == null) {
             return false;
         }
-        Configuration config = activity.getResources().getConfiguration();
+        Configuration config = appContext.getResources().getConfiguration();
         return update(config.orientation);
     }
 
     /*
      * Update screen orientation given the android orientation.
      * Retrieve rotation via GeckoAppShell.
      *
      * @param aAndroidOrientation
@@ -160,83 +161,62 @@ public class GeckoScreenOrientation {
     /*
      * @return The Gecko screen orientation derived from Android orientation and
      *         rotation.
      */
     public ScreenOrientation getScreenOrientation() {
         return mScreenOrientation;
     }
 
-    /*
+    /**
      * Lock screen orientation given the Gecko screen orientation.
      *
      * @param aGeckoOrientation
      *        The Gecko orientation provided.
      */
     public void lock(int aGeckoOrientation) {
         lock(ScreenOrientation.get(aGeckoOrientation));
     }
 
-    /*
+    /**
      * Lock screen orientation given the Gecko screen orientation.
-     * Retrieve rotation via GeckoAppShell.
      *
      * @param aScreenOrientation
      *        Gecko screen orientation derived from Android orientation and
      *        rotation.
      *
      * @return Whether the locking was successful.
      */
     public boolean lock(ScreenOrientation aScreenOrientation) {
         Log.d(LOGTAG, "locking to " + aScreenOrientation);
-        update(aScreenOrientation);
-        return setRequestedOrientation(aScreenOrientation);
+        final ScreenOrientationDelegate delegate = GeckoAppShell.getScreenOrientationDelegate();
+        final int activityInfoOrientation = screenOrientationToActivityInfoOrientation(aScreenOrientation);
+        if (delegate.setRequestedOrientationForCurrentActivity(activityInfoOrientation)) {
+            update(aScreenOrientation);
+            return true;
+        } else {
+            return false;
+        }
     }
 
-    /*
+    /**
      * Unlock and update screen orientation.
      *
      * @return Whether the unlocking was successful.
      */
     public boolean unlock() {
         Log.d(LOGTAG, "unlocking");
-        setRequestedOrientation(mDefaultScreenOrientation);
-        return update();
-    }
-
-    private Activity getActivity() {
-        if (GeckoAppShell.getGeckoInterface() == null) {
-            return null;
-        }
-        return GeckoAppShell.getGeckoInterface().getActivity();
-    }
-
-    /*
-     * Set the given requested orientation for the current activity.
-     * This is essentially an unlock without an update.
-     *
-     * @param aScreenOrientation
-     *        Gecko screen orientation.
-     *
-     * @return Whether the requested orientation was set. This can only fail if
-     *         the current activity cannot be retrieved via GeckoAppShell.
-     *
-     */
-    private boolean setRequestedOrientation(ScreenOrientation aScreenOrientation) {
-        int activityOrientation = screenOrientationToActivityInfoOrientation(aScreenOrientation);
-        Activity activity = getActivity();
-        if (activity == null) {
-            Log.w(LOGTAG, "setRequestOrientation: failed to get activity");
+        final ScreenOrientationDelegate delegate = GeckoAppShell.getScreenOrientationDelegate();
+        final int activityInfoOrientation = screenOrientationToActivityInfoOrientation(ScreenOrientation.DEFAULT);
+        if (delegate.setRequestedOrientationForCurrentActivity(activityInfoOrientation)) {
+            update();
+            return true;
+        } else {
             return false;
         }
-        if (activity.getRequestedOrientation() == activityOrientation) {
-            return false;
-        }
-        activity.setRequestedOrientation(activityOrientation);
-        return true;
     }
 
     /*
      * Combine the Android orientation and rotation to the Gecko orientation.
      *
      * @param aAndroidOrientation
      *        Android orientation from Configuration.orientation.
      * @param aRotation
@@ -280,25 +260,26 @@ public class GeckoScreenOrientation {
                 return 270;
             default:
                 Log.w(LOGTAG, "getAngle: unexpected rotation value");
                 return 0;
         }
     }
 
     /*
-     * @return Device rotation from Display.getRotation().
+     * @return Device rotation.
      */
     private int getRotation() {
-        Activity activity = getActivity();
-        if (activity == null) {
-            Log.w(LOGTAG, "getRotation: failed to get activity");
+        final Context appContext = GeckoAppShell.getApplicationContext();
+        if (appContext == null) {
             return DEFAULT_ROTATION;
         }
-        return activity.getWindowManager().getDefaultDisplay().getRotation();
+        final WindowManager windowManager =
+                (WindowManager) appContext.getSystemService(Context.WINDOW_SERVICE);
+        return windowManager.getDefaultDisplay().getRotation();
     }
 
     /*
      * Retrieve the screen orientation from an array string.
      *
      * @param aArray
      *        String containing comma-delimited strings.
      *
@@ -366,17 +347,16 @@ public class GeckoScreenOrientation {
                 return Configuration.ORIENTATION_LANDSCAPE;
             case NONE:
             case DEFAULT:
             default:
                 return Configuration.ORIENTATION_UNDEFINED;
         }
     }
 
-
     /*
      * Convert Gecko screen orientation to Android ActivityInfo orientation.
      * This is yet another orientation used by Android, but it's more detailed
      * than the Android orientation.
      * It is required for screen orientation locking and unlocking.
      *
      * @param aScreenOrientation
      *        Gecko screen orientation.
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/ScreenOrientationDelegate.java
@@ -0,0 +1,26 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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;
+
+/**
+ * A <code>ScreenOrientationDelegate</code> is responsible for setting the screen orientation.
+ * <p>
+ * A browser that wants to support the <a href="https://w3c.github.io/screen-orientation/">Screen
+ * Orientation API</a> MUST implement these methods.  A GeckoView consumer MAY implement these
+ * methods.
+ * <p> To implement, consider registering an
+ * {@link android.app.Application.ActivityLifecycleCallbacks} handler to track the current
+ * foreground {@link android.app.Activity}.
+ */
+public interface ScreenOrientationDelegate {
+    /**
+     * If possible, set the current screen orientation.
+     *
+     * @param requestedActivityInfoOrientation An orientation constant as used in {@link android.content.pm.ActivityInfo#screenOrientation}.
+     * @return true if screen orientation could be set; false otherwise.
+     */
+    boolean setRequestedOrientationForCurrentActivity(int requestedActivityInfoOrientation);
+}