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)); +}