1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- 2 * This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 package org.mozilla.gecko; 7 8 import android.content.ContentResolver; 9 import android.content.Context; 10 import android.content.res.Configuration; 11 import android.database.ContentObserver; 12 import android.hardware.input.InputManager; 13 import android.net.Uri; 14 import android.os.Build; 15 import android.os.Handler; 16 import android.os.Looper; 17 import android.provider.Settings; 18 import androidx.annotation.RequiresApi; 19 import android.util.Log; 20 import android.view.InputDevice; 21 import org.mozilla.gecko.annotation.WrapForJNI; 22 import org.mozilla.gecko.util.InputDeviceUtils; 23 import org.mozilla.gecko.util.ThreadUtils; 24 25 public class GeckoSystemStateListener 26 implements InputManager.InputDeviceListener { 27 private static final String LOGTAG = "SystemStateListener"; 28 29 private static final GeckoSystemStateListener listenerInstance = new GeckoSystemStateListener(); 30 31 private boolean mInitialized; 32 private ContentObserver mContentObserver; 33 private static Context sApplicationContext; 34 private InputManager mInputManager; 35 private boolean mIsNightMode; 36 getInstance()37 public static GeckoSystemStateListener getInstance() { 38 return listenerInstance; 39 } 40 GeckoSystemStateListener()41 private GeckoSystemStateListener() { 42 } 43 initialize(final Context context)44 public synchronized void initialize(final Context context) { 45 if (mInitialized) { 46 Log.w(LOGTAG, "Already initialized!"); 47 return; 48 } 49 mInputManager = (InputManager) 50 context.getSystemService(Context.INPUT_SERVICE); 51 mInputManager.registerInputDeviceListener(listenerInstance, ThreadUtils.getUiHandler()); 52 53 sApplicationContext = context; 54 final ContentResolver contentResolver = sApplicationContext.getContentResolver(); 55 final Uri animationSetting = Settings.System.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE); 56 mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) { 57 @Override 58 public void onChange(final boolean selfChange) { 59 onDeviceChanged(); 60 } 61 }; 62 contentResolver.registerContentObserver(animationSetting, false, mContentObserver); 63 64 mIsNightMode = (sApplicationContext.getResources().getConfiguration().uiMode & 65 Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; 66 67 mInitialized = true; 68 } 69 shutdown()70 public synchronized void shutdown() { 71 if (!mInitialized) { 72 Log.w(LOGTAG, "Already shut down!"); 73 return; 74 } 75 76 if (mInputManager != null) { 77 Log.e(LOGTAG, "mInputManager should be valid!"); 78 return; 79 } 80 81 mInputManager.unregisterInputDeviceListener(listenerInstance); 82 83 final ContentResolver contentResolver = sApplicationContext.getContentResolver(); 84 contentResolver.unregisterContentObserver(mContentObserver); 85 86 mInitialized = false; 87 mInputManager = null; 88 mContentObserver = null; 89 } 90 91 @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) 92 @WrapForJNI(calledFrom = "gecko") 93 /** 94 * For prefers-reduced-motion media queries feature. 95 * 96 * Uses `Settings.Global` which was introduced in API version 17. 97 */ prefersReducedMotion()98 private static boolean prefersReducedMotion() { 99 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { 100 return false; 101 } 102 103 final ContentResolver contentResolver = sApplicationContext.getContentResolver(); 104 105 return Settings.Global.getFloat(contentResolver, 106 Settings.Global.ANIMATOR_DURATION_SCALE, 107 1) == 0.0f; 108 } 109 110 /** 111 * For prefers-color-scheme media queries feature. 112 */ isNightMode()113 public boolean isNightMode() { 114 return mIsNightMode; 115 } 116 updateNightMode(final int newUIMode)117 public void updateNightMode(final int newUIMode) { 118 final boolean isNightMode = (newUIMode & Configuration.UI_MODE_NIGHT_MASK) 119 == Configuration.UI_MODE_NIGHT_YES; 120 if (isNightMode == mIsNightMode) { 121 return; 122 } 123 mIsNightMode = isNightMode; 124 onDeviceChanged(); 125 } 126 127 @WrapForJNI(stubName = "OnDeviceChanged", calledFrom = "any", dispatchTo = "gecko") nativeOnDeviceChanged()128 private static native void nativeOnDeviceChanged(); 129 onDeviceChanged()130 public static void onDeviceChanged() { 131 if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) { 132 nativeOnDeviceChanged(); 133 } else { 134 GeckoThread.queueNativeCallUntil( 135 GeckoThread.State.PROFILE_READY, GeckoSystemStateListener.class, 136 "nativeOnDeviceChanged"); 137 } 138 } 139 notifyDeviceChanged(final int deviceId)140 private void notifyDeviceChanged(final int deviceId) { 141 final InputDevice device = InputDevice.getDevice(deviceId); 142 if (device == null || 143 !InputDeviceUtils.isPointerTypeDevice(device)) { 144 return; 145 } 146 onDeviceChanged(); 147 } 148 149 @Override onInputDeviceAdded(final int deviceId)150 public void onInputDeviceAdded(final int deviceId) { 151 notifyDeviceChanged(deviceId); 152 } 153 154 @Override onInputDeviceRemoved(final int deviceId)155 public void onInputDeviceRemoved(final int deviceId) { 156 // Call onDeviceChanged directly without checking device source types 157 // since we can no longer get a valid `InputDevice` in the case of 158 // device removal. 159 onDeviceChanged(); 160 } 161 162 @Override onInputDeviceChanged(final int deviceId)163 public void onInputDeviceChanged(final int deviceId) { 164 notifyDeviceChanged(deviceId); 165 } 166 } 167