Bug 1532850 - Implement the backend for prefers-color-scheme on Android. r=geckoview-reviewers,snorp a=pascalc
authorHiroyuki Ikezoe <hikezoe@mozilla.com>
Tue, 19 Mar 2019 10:10:52 +0000
changeset 525819 c744534702892c60350398ce2d3bd41ebdd3f43b
parent 525818 33a8d0d55cf4b7d419a8c5803a9ac234d3d330fb
child 525820 44b4f787bfeb2e6888e58488dfec84acafc15941
push id2032
push userffxbld-merge
push dateMon, 13 May 2019 09:36:57 +0000
treeherdermozilla-release@455c1065dcbe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgeckoview-reviewers, snorp, pascalc
bugs1532850
milestone67.0
Bug 1532850 - Implement the backend for prefers-color-scheme on Android. r=geckoview-reviewers,snorp a=pascalc Differential Revision: https://phabricator.services.mozilla.com/D22272
mobile/android/base/AndroidManifest.xml.in
mobile/android/geckoview/api.txt
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoSystemStateListener.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
widget/android/nsLookAndFeel.cpp
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -53,17 +53,17 @@
              org.mozilla.{fennec,firefox,firefox_beta}.  The internal Java
              package hierarchy inside the Android package used to have an
              org.mozilla.{fennec,firefox,firefox_beta} subtree *and* an
              org.mozilla.gecko subtree; it now only has org.mozilla.gecko. -->
         <activity android:name="@MOZ_ANDROID_BROWSER_INTENT_CLASS@"
                   android:label="@string/moz_app_displayname"
                   android:taskAffinity="@ANDROID_PACKAGE_NAME@.BROWSER"
                   android:alwaysRetainTaskState="true"
-                  android:configChanges="keyboard|keyboardHidden|mcc|mnc|orientation|screenSize|locale|layoutDirection|smallestScreenSize|screenLayout"
+                  android:configChanges="keyboard|keyboardHidden|mcc|mnc|orientation|screenSize|locale|layoutDirection|smallestScreenSize|screenLayout|uiMode"
                   android:resizeableActivity="true"
                   android:supportsPictureInPicture="true"
                   android:windowSoftInputMode="stateUnspecified|adjustResize"
                   android:launchMode="singleTask"
                   android:exported="true"
                   android:theme="@style/Gecko.App" />
 
         <!-- Bug 1256615 / Bug 1268455: We published an .App alias and we need to maintain it
@@ -347,17 +347,17 @@
                 <action android:name="android.intent.action.SEND" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <data android:mimeType="text/plain" />
             </intent-filter>
 
         </activity>
 
         <activity android:name="org.mozilla.gecko.customtabs.CustomTabsActivity"
-                  android:configChanges="keyboard|keyboardHidden|mcc|mnc|orientation|screenSize|locale|layoutDirection|smallestScreenSize|screenLayout"
+                  android:configChanges="keyboard|keyboardHidden|mcc|mnc|orientation|screenSize|locale|layoutDirection|smallestScreenSize|screenLayout|uiMode"
                   android:windowSoftInputMode="stateUnspecified|adjustResize"
                   android:theme="@style/GeckoCustomTabs" />
 
         <activity android:name="org.mozilla.gecko.webapps.WebAppActivity"
             android:theme="@style/Theme.AppCompat.NoActionBar" />
 
         <!-- Declare a predefined number of WebApp<num> activities. These are
              used so that each web app can launch in its own activity. -->
--- a/mobile/android/geckoview/api.txt
+++ b/mobile/android/geckoview/api.txt
@@ -172,16 +172,17 @@ package org.mozilla.geckoview {
 
   public static final class GeckoResult.UncaughtException extends java.lang.RuntimeException {
     ctor public UncaughtException(java.lang.Throwable);
   }
 
   public final class GeckoRuntime implements android.os.Parcelable {
     ctor public GeckoRuntime();
     method @android.support.annotation.UiThread public void attachTo(@android.support.annotation.NonNull android.content.Context);
+    method @android.support.annotation.UiThread public void configurationChanged(@android.support.annotation.NonNull android.content.res.Configuration);
     method @android.support.annotation.UiThread @android.support.annotation.NonNull public static org.mozilla.geckoview.GeckoRuntime create(@android.support.annotation.NonNull android.content.Context);
     method @android.support.annotation.UiThread @android.support.annotation.NonNull public static org.mozilla.geckoview.GeckoRuntime create(@android.support.annotation.NonNull android.content.Context, @android.support.annotation.NonNull org.mozilla.geckoview.GeckoRuntimeSettings);
     method @android.support.annotation.UiThread @android.support.annotation.NonNull public static synchronized org.mozilla.geckoview.GeckoRuntime getDefault(@android.support.annotation.NonNull android.content.Context);
     method @android.support.annotation.UiThread @android.support.annotation.Nullable public org.mozilla.geckoview.GeckoRuntime.Delegate getDelegate();
     method @android.support.annotation.UiThread @android.support.annotation.Nullable public java.io.File getProfileDir();
     method @android.support.annotation.AnyThread @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoRuntimeSettings getSettings();
     method @android.support.annotation.UiThread @android.support.annotation.NonNull public org.mozilla.geckoview.RuntimeTelemetry getTelemetry();
     method @android.support.annotation.UiThread public void orientationChanged();
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoSystemStateListener.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoSystemStateListener.java
@@ -2,16 +2,17 @@
  * 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.content.ContentResolver;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.database.ContentObserver;
 import android.hardware.input.InputManager;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.provider.Settings;
 import android.support.annotation.RequiresApi;
@@ -26,16 +27,17 @@ public class GeckoSystemStateListener
     private static final String LOGTAG = "GeckoSystemStateListener";
 
     private static final GeckoSystemStateListener listenerInstance = new GeckoSystemStateListener();
 
     private boolean mInitialized;
     private ContentObserver mContentObserver;
     private static Context sApplicationContext;
     private InputManager mInputManager;
+    private static boolean sIsNightMode;
 
     public static GeckoSystemStateListener getInstance() {
         return listenerInstance;
     }
 
     private GeckoSystemStateListener() {
     }
 
@@ -54,16 +56,19 @@ public class GeckoSystemStateListener
         mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
             @Override
             public void onChange(final boolean selfChange) {
                 onDeviceChanged();
             }
         };
         contentResolver.registerContentObserver(animationSetting, false, mContentObserver);
 
+        sIsNightMode = (sApplicationContext.getResources().getConfiguration().uiMode &
+            Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
+
         mInitialized = true;
     }
 
     public synchronized void shutdown() {
         if (!mInitialized) {
             Log.w(LOGTAG, "Already shut down!");
             return;
         }
@@ -104,16 +109,34 @@ public class GeckoSystemStateListener
 
     @WrapForJNI(calledFrom = "gecko")
     private static void notifyPrefersReducedMotionChangedForTest() {
         ContentResolver contentResolver = sApplicationContext.getContentResolver();
         Uri animationSetting = Settings.System.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE);
         contentResolver.notifyChange(animationSetting, null);
     }
 
+    @WrapForJNI(calledFrom = "gecko")
+    /**
+     * For prefers-color-scheme media queries feature.
+     */
+    private static boolean isNightMode() {
+        return sIsNightMode;
+    }
+
+    public void updateNightMode(final int newUIMode) {
+        boolean isNightMode = (newUIMode & Configuration.UI_MODE_NIGHT_MASK)
+            == Configuration.UI_MODE_NIGHT_YES;
+        if (isNightMode == sIsNightMode) {
+            return;
+        }
+        sIsNightMode = isNightMode;
+        onDeviceChanged();
+    }
+
     @WrapForJNI(stubName = "OnDeviceChanged", calledFrom = "ui", dispatchTo = "gecko")
     private static native void nativeOnDeviceChanged();
 
     private static void onDeviceChanged() {
         if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
             nativeOnDeviceChanged();
         } else {
             GeckoThread.queueNativeCallUntil(
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java
@@ -6,16 +6,17 @@
 
 package org.mozilla.geckoview;
 
 import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
+import android.content.res.Configuration;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.content.Context;
 import android.os.Process;
 import android.support.annotation.AnyThread;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
@@ -429,16 +430,27 @@ public final class GeckoRuntime implemen
      */
     @UiThread
     public void orientationChanged() {
         ThreadUtils.assertOnUiThread();
         GeckoScreenOrientation.getInstance().update();
     }
 
     /**
+     * Notify Gecko that the device configuration has changed.
+     * @param newConfig The new Configuration object,
+     *                  {@link android.content.res.Configuration}.
+     */
+    @UiThread
+    public void configurationChanged(final @NonNull Configuration newConfig) {
+        ThreadUtils.assertOnUiThread();
+        GeckoSystemStateListener.getInstance().updateNightMode(newConfig.uiMode);
+    }
+
+    /**
      * Notify Gecko that the screen orientation has changed.
      * @param newOrientation The new screen orientation, as retrieved e.g. from the current
      *                       {@link android.content.res.Configuration}.
      */
     @UiThread
     public void orientationChanged(final int newOrientation) {
         ThreadUtils.assertOnUiThread();
         GeckoScreenOrientation.getInstance().update(newOrientation);
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
@@ -460,16 +460,17 @@ public class GeckoView extends FrameLayo
     protected void onConfigurationChanged(final Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
 
         if (mRuntime != null) {
             // onConfigurationChanged is not called for 180 degree orientation changes,
             // we will miss such rotations and the screen orientation will not be
             // updated.
             mRuntime.orientationChanged(newConfig.orientation);
+            mRuntime.configurationChanged(newConfig);
         }
     }
 
     @Override
     public boolean gatherTransparentRegion(final Region region) {
         // For detecting changes in SurfaceView layout, we take a shortcut here and
         // override gatherTransparentRegion, instead of registering a layout listener,
         // which is more expensive.
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
@@ -3,16 +3,22 @@ layout: default
 title: API Changelog
 description: GeckoView API Changelog.
 nav_exclude: true
 exclude: true
 ---
 
 <h1> GeckoView API Changelog. </h1>
 
+## v68
+- Added [`GeckoRuntime#configurationChanged`][68.1] to notify the device
+  configuration has changed.
+
+[68.1]: ../GeckoRuntime.html#configurationChanged
+
 ## v67
 - Added [`setAutomaticFontSizeAdjustment`][67.2] to
   [`GeckoRuntimeSettings`][67.3] for automatically adjusting font size settings
   depending on the OS-level font size setting.
 
 [67.2]: ../GeckoRuntimeSettings.html#setAutomaticFontSizeAdjustment-boolean-
 [67.3]: ../GeckoRuntimeSettings.html
 
@@ -204,9 +210,9 @@ exclude: true
 [65.23]: ../GeckoSession.FinderResult.html
 
 - Update [`CrashReporter#sendCrashReport`][65.24] to return the crash ID as a
   [`GeckoResult<String>`][65.25].
 
 [65.24]: ../CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
 [65.25]: ../GeckoResult.html
 
-[api-version]: 09c473360eb5e17aa801fa0f966cd8671cf2f3d2
+[api-version]: e1330c0e7cfa08420041813f07f24a9389020564
--- a/widget/android/nsLookAndFeel.cpp
+++ b/widget/android/nsLookAndFeel.cpp
@@ -395,16 +395,26 @@ nsresult nsLookAndFeel::GetIntImpl(IntID
 
     case eIntID_PrimaryPointerCapabilities:
       aResult = java::GeckoAppShell::GetPrimaryPointerCapabilities();
       break;
     case eIntID_AllPointerCapabilities:
       aResult = java::GeckoAppShell::GetAllPointerCapabilities();
       break;
 
+    case eIntID_SystemUsesDarkTheme:
+      // Bail out if AndroidBridge hasn't initialized since we try to query
+      // this vailue via nsMediaFeatures::InitSystemMetrics without initializing
+      // AndroidBridge on xpcshell tests.
+      if (!jni::IsAvailable()) {
+        return NS_ERROR_FAILURE;
+      }
+      aResult = java::GeckoSystemStateListener::IsNightMode() ? 1 : 0;
+      break;
+
     default:
       aResult = 0;
       rv = NS_ERROR_FAILURE;
   }
 
   return rv;
 }