diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 9525c788e..f9f00c6c3 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -11,6 +11,7 @@
+
diff --git a/android/app/src/main/java/org/jitsi/meet/MainApplication.java b/android/app/src/main/java/org/jitsi/meet/MainApplication.java
index 0d3395fa2..967de1b6e 100644
--- a/android/app/src/main/java/org/jitsi/meet/MainApplication.java
+++ b/android/app/src/main/java/org/jitsi/meet/MainApplication.java
@@ -32,7 +32,8 @@ public class MainApplication extends Application implements ReactApplication {
new com.ocetnik.timer.BackgroundTimerPackage(),
new com.oney.WebRTCModule.WebRTCModulePackage(),
new com.rnimmersive.RNImmersivePackage(),
- new org.jitsi.meet.audiomode.AudioModePackage()
+ new org.jitsi.meet.audiomode.AudioModePackage(),
+ new org.jitsi.meet.proximity.ProximityPackage()
);
}
};
diff --git a/android/app/src/main/java/org/jitsi/meet/proximity/ProximityModule.java b/android/app/src/main/java/org/jitsi/meet/proximity/ProximityModule.java
new file mode 100644
index 000000000..2dccd8378
--- /dev/null
+++ b/android/app/src/main/java/org/jitsi/meet/proximity/ProximityModule.java
@@ -0,0 +1,101 @@
+package org.jitsi.meet.proximity;
+
+import android.content.Context;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactContextBaseJavaModule;
+import com.facebook.react.bridge.ReactMethod;
+import com.facebook.react.bridge.UiThreadUtil;
+
+/**
+ * Module implementing a simple API to enable a proximity sensor-controlled
+ * wake lock. When the lock is held, if the proximity sensor detects a nearby
+ * object it will dim the screen and disable touch controls. The functionality
+ * is used with the conference audio-only mode.
+ */
+public class ProximityModule extends ReactContextBaseJavaModule {
+ /**
+ * React Native module name.
+ */
+ private static final String MODULE_NAME = "Proximity";
+
+ /**
+ * This type of wake lock (the one activated by the proximity sensor) has
+ * been available for a while, but the constant was only exported in API
+ * level 21 (Android Marshmallow) so make no assumptions and use its value
+ * directly.
+ *
+ * TODO: Remove when we bump the API level to 21.
+ */
+ private static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = 32;
+
+ /**
+ * {@link WakeLock} instance.
+ */
+ private final WakeLock wakeLock;
+
+ /**
+ * Initializes a new module instance. There shall be a single instance of
+ * this module throughout the lifetime of the application.
+ *
+ * @param reactContext The {@link ReactApplicationContext} where this module
+ * is created.
+ */
+ public ProximityModule(ReactApplicationContext reactContext) {
+ super(reactContext);
+
+ WakeLock wakeLock;
+ PowerManager powerManager
+ = (PowerManager)
+ reactContext.getSystemService(Context.POWER_SERVICE);
+
+ try {
+ wakeLock
+ = powerManager.newWakeLock(
+ PROXIMITY_SCREEN_OFF_WAKE_LOCK,
+ MODULE_NAME);
+ } catch (Throwable ignored) {
+ wakeLock = null;
+ }
+
+ this.wakeLock = wakeLock;
+ }
+
+ /**
+ * Gets the name of this module to be used in the React Native bridge.
+ *
+ * @return The name of this module to be used in the React Native bridge.
+ */
+ @Override
+ public String getName() {
+ return MODULE_NAME;
+ }
+
+ /**
+ * Acquires / releases the proximity sensor wake lock.
+ *
+ * @param enabled {@code true} to enable the proximity sensor; otherwise,
+ * {@code false}.
+ */
+ @ReactMethod
+ public void setEnabled(final boolean enabled) {
+ if (wakeLock == null) {
+ return;
+ }
+
+ UiThreadUtil.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (enabled) {
+ if (!wakeLock.isHeld()) {
+ wakeLock.acquire();
+ }
+ } else if (wakeLock.isHeld()) {
+ wakeLock.release();
+ }
+ }
+ });
+ }
+}
diff --git a/android/app/src/main/java/org/jitsi/meet/proximity/ProximityPackage.java b/android/app/src/main/java/org/jitsi/meet/proximity/ProximityPackage.java
new file mode 100644
index 000000000..ca2ccec49
--- /dev/null
+++ b/android/app/src/main/java/org/jitsi/meet/proximity/ProximityPackage.java
@@ -0,0 +1,48 @@
+package org.jitsi.meet.proximity;
+
+import com.facebook.react.ReactPackage;
+import com.facebook.react.bridge.JavaScriptModule;
+import com.facebook.react.bridge.NativeModule;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.uimanager.ViewManager;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Implements {@link ReactPackage} for {@link ProximityModule}.
+ */
+public class ProximityPackage implements ReactPackage {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List> createJSModules() {
+ return Collections.emptyList();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return List of native modules to be exposed by React Native.
+ */
+ @Override
+ public List createNativeModules(
+ ReactApplicationContext reactContext) {
+ List modules = new ArrayList<>();
+
+ modules.add(new ProximityModule(reactContext));
+
+ return modules;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List createViewManagers(
+ ReactApplicationContext reactContext) {
+ return Collections.emptyList();
+ }
+}
diff --git a/ios/app/Proximity.m b/ios/app/Proximity.m
new file mode 100644
index 000000000..bae26f334
--- /dev/null
+++ b/ios/app/Proximity.m
@@ -0,0 +1,24 @@
+#import "RCTBridgeModule.h"
+
+#import
+
+@interface Proximity : NSObject
+@end
+
+@implementation Proximity
+
+RCT_EXPORT_MODULE();
+
+/**
+ * Enables / disables the proximity sensor monitoring. On iOS enabling the
+ * proximity sensor automatically dims the screen and disables touch controls,
+ * so there is nothing else to do (unlike on Android)!
+ *
+ * @param enabled {@code YES} to enable proximity (sensor) monitoring;
+ * {@code NO}, otherwise.
+ */
+RCT_EXPORT_METHOD(setEnabled:(BOOL)enabled) {
+ [[UIDevice currentDevice] setProximityMonitoringEnabled:enabled];
+}
+
+@end
diff --git a/ios/jitsi-meet-react.xcodeproj/project.pbxproj b/ios/jitsi-meet-react.xcodeproj/project.pbxproj
index 3dd9a2e79..1948729f2 100644
--- a/ios/jitsi-meet-react.xcodeproj/project.pbxproj
+++ b/ios/jitsi-meet-react.xcodeproj/project.pbxproj
@@ -14,6 +14,7 @@
00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; };
00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; };
0B42DFAE1E2FD90700111B12 /* AudioMode.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B42DFAD1E2FD90700111B12 /* AudioMode.m */; };
+ 0B96CAF11E8CF0E8005F348C /* Proximity.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B96CAF01E8CF0E8005F348C /* Proximity.m */; };
133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 78C398B91ACF4ADC00677621 /* libRCTLinking.a */; };
139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */; };
139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */; };
@@ -273,6 +274,7 @@
00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = "../node_modules/react-native/Libraries/Vibration/RCTVibration.xcodeproj"; sourceTree = ""; };
0965153BB98645B4A8B6AA10 /* RNBackgroundTimer.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNBackgroundTimer.xcodeproj; path = "../node_modules/react-native-background-timer/ios/RNBackgroundTimer.xcodeproj"; sourceTree = ""; };
0B42DFAD1E2FD90700111B12 /* AudioMode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AudioMode.m; path = app/AudioMode.m; sourceTree = ""; };
+ 0B96CAF01E8CF0E8005F348C /* Proximity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Proximity.m; path = app/Proximity.m; sourceTree = ""; };
0EA8C046B2BF46279796F07D /* libKCKeepAwake.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libKCKeepAwake.a; sourceTree = ""; };
139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = "../node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj"; sourceTree = ""; };
139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = "../node_modules/react-native/Libraries/WebSocket/RCTWebSocket.xcodeproj"; sourceTree = ""; };
@@ -426,6 +428,7 @@
008F07F21AC5B25A0029DE68 /* main.jsbundle */,
13B07FB71A68108700A75B9A /* main.m */,
B3A9D0241E0481E10009343D /* POSIX.m */,
+ 0B96CAF01E8CF0E8005F348C /* Proximity.m */,
);
name = app;
sourceTree = "";
@@ -945,6 +948,7 @@
buildActionMask = 2147483647;
files = (
B3A9D0251E0481E10009343D /* POSIX.m in Sources */,
+ 0B96CAF11E8CF0E8005F348C /* Proximity.m in Sources */,
0B42DFAE1E2FD90700111B12 /* AudioMode.m in Sources */,
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
diff --git a/react/features/app/components/App.native.js b/react/features/app/components/App.native.js
index ba7cae73f..b5c6f6e39 100644
--- a/react/features/app/components/App.native.js
+++ b/react/features/app/components/App.native.js
@@ -6,6 +6,7 @@ import { Platform } from '../../base/react';
import '../../mobile/audio-mode';
import '../../mobile/background';
import '../../mobile/full-screen';
+import '../../mobile/proximity';
import '../../mobile/wake-lock';
import { AbstractApp } from './AbstractApp';
diff --git a/react/features/mobile/proximity/index.js b/react/features/mobile/proximity/index.js
new file mode 100644
index 000000000..d43689289
--- /dev/null
+++ b/react/features/mobile/proximity/index.js
@@ -0,0 +1 @@
+import './middleware';
diff --git a/react/features/mobile/proximity/middleware.js b/react/features/mobile/proximity/middleware.js
new file mode 100644
index 000000000..9cd20f08e
--- /dev/null
+++ b/react/features/mobile/proximity/middleware.js
@@ -0,0 +1,53 @@
+import { NativeModules } from 'react-native';
+
+import {
+ CONFERENCE_FAILED,
+ CONFERENCE_JOINED,
+ CONFERENCE_LEFT,
+ SET_AUDIO_ONLY
+} from '../../base/conference';
+import { MiddlewareRegistry } from '../../base/redux';
+
+/**
+ * Middleware which enables / disables the proximity sensor in accord with
+ * conference-related actions. If the proximity sensor is enabled, it will dim
+ * the screen and disable touch controls when an object is nearby. The
+ * functionality is enabled when a conference is in audio-only mode.
+ *
+ * @param {Store} store - Redux store.
+ * @returns {Function}
+ */
+MiddlewareRegistry.register(store => next => action => {
+ switch (action.type) {
+ case CONFERENCE_JOINED: {
+ const { audioOnly } = store.getState()['features/base/conference'];
+
+ _setProximityEnabled(audioOnly);
+ break;
+ }
+
+ case CONFERENCE_FAILED:
+ case CONFERENCE_LEFT:
+ _setProximityEnabled(false);
+ break;
+
+ case SET_AUDIO_ONLY:
+ _setProximityEnabled(action.audioOnly);
+ break;
+ }
+
+ return next(action);
+});
+
+/**
+ * Enables / disables the proximity sensor. If the proximity sensor is enabled,
+ * it will dim the screen and disable touch controls when an object is nearby.
+ *
+ * @param {boolean} enabled - True to enable the proximity sensor or false to
+ * disable it.
+ * @private
+ * @returns {void}
+ */
+function _setProximityEnabled(enabled) {
+ NativeModules.Proximity.setEnabled(Boolean(enabled));
+}