Compare commits

..

1 Commits

Author SHA1 Message Date
Aaron van Meerten
4ed9d5893b util updates 2019-09-09 08:42:00 -05:00
1137 changed files with 29473 additions and 58637 deletions

View File

@@ -2,30 +2,22 @@
; We fork some components by platform
.*/*[.]android.js
; Ignore "BUCK" generated dirs
<PROJECT_ROOT>/\.buckd/
; Ignore unexpected extra "@providesModule"
.*/node_modules/.*/node_modules/fbjs/.*
node_modules/react-native/Libraries/react-native/React.js
; Ignore duplicate module providers
; For RN Apps installed via npm, "Libraries" folder is inside
; "node_modules/react-native" but in the source repo it is in the root
node_modules/react-native/Libraries/react-native/React.js
; Flow doesn't support platforms
.*/Libraries/Utilities/LoadingView.js
.*/Libraries/react-native/React.js
; Ignore polyfills
node_modules/react-native/Libraries/polyfills/.*
.*/Libraries/polyfills/.*
; These should not be required directly
; require from fbjs/lib instead: require('fbjs/lib/warning')
node_modules/warning/.*
; Flow doesn't support platforms
.*/Libraries/Utilities/HMRLoadingView.js
[untyped]
.*/node_modules/@react-native-community/cli/.*/.*
; Ignore metro
.*/node_modules/metro/.*
; Ignore packages in node_modules which we (i.e. the jitsi-meet project) have
; seen to cause errors and we have chosen not to fix.
@@ -48,18 +40,6 @@ emoji=true
esproposal.optional_chaining=enable
esproposal.nullish_coalescing=enable
; We (i.e. the jitsi-meet project) are using the haste module system on Web as
; well, not only on React Native. Unfortunately, Flow does not support .web.js
; by default. Override Flow's defaults to include .web.js as well. Technically,
; we have .native.js as well so the choice of .web.js may lead to errors.
; Practically though, it is a potential future problem that we do not have at
; the time of this writing.
module.file_ext=.web.js
; Flow's defaults:
module.file_ext=.js
module.file_ext=.json
module.file_ext=.ios.js
module.system=haste
module.system.haste.use_name_reducers=true
# get basename
@@ -72,11 +52,8 @@ module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1'
module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1'
module.system.haste.paths.blacklist=.*/__tests__/.*
module.system.haste.paths.blacklist=.*/__mocks__/.*
module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/Libraries/.*
module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/RNTester/.*
module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/IntegrationTests/.*
module.system.haste.paths.blacklist=<PROJECT_ROOT>/node_modules/react-native/Libraries/react-native/react-native-implementation.js
module.system.haste.paths.blacklist=<PROJECT_ROOT>/node_modules/react-native/Libraries/Animated/src/polyfills/.*
module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/Libraries/.*
munge_underscores=true
@@ -87,32 +64,22 @@ suppress_type=$FlowFixMe
suppress_type=$FlowFixMeProps
suppress_type=$FlowFixMeState
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
[lints]
sketchy-null-number=warn
sketchy-null-mixed=warn
sketchy-number=warn
untyped-type-import=warn
nonstrict-import=warn
deprecated-type=warn
unsafe-getters-setters=warn
inexact-spread=warn
unnecessary-invariant=warn
signature-verification-failure=warn
deprecated-utility=error
[strict]
deprecated-type
nonstrict-import
sketchy-null
unclear-type
unsafe-getters-setters
untyped-import
untyped-type-import
; We (i.e. the jitsi-meet project) are using the haste module system on Web as
; well, not only on React Native. Unfortunately, Flow does not support .web.js
; by default. Override Flow's defaults to include .web.js as well. Technically,
; we have .native.js as well so the choice of .web.js may lead to errors.
; Practically though, it is a potential future problem that we do not have at
; the time of this writing.
module.file_ext=.web.js
; Flow's defaults:
module.file_ext=.js
module.file_ext=.jsx
module.file_ext=.json
[version]
^0.104.0
^0.92.0

View File

@@ -1,11 +0,0 @@
---
name: Security issues
about: Please email security@jitsi.org
---
We take security very seriously and develop all Jitsi projects to be secure and safe.
If you find (or simply suspect) a security issue in any of the Jitsi projects, please send us an email to security@jitsi.org.
We encourage responsible disclosure for the sake of our users, so please reach out before posting in a public space.

View File

@@ -1,15 +0,0 @@
name: Linter
on: [pull_request]
jobs:
run-linter:
name: Run Linter
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: '12.x'
- run: npm install
- run: npm run lint

7
.gitignore vendored
View File

@@ -61,13 +61,18 @@ buck-out/
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/
*/fastlane/report.xml
*/fastlane/Preview.html
*/fastlane/screenshots
# Build artifacts
*.jsbundle
*.framework
android/app/debug
android/app/release
# precommit-hook

View File

@@ -1,6 +1,4 @@
osx_image: xcode11.1
osx_image: xcode10.2
language: objective-c
script:
- "./ios/travis-ci/build-ipa.sh"
after_script:
- sleep 10

View File

@@ -3,7 +3,6 @@ CLEANCSS = ./node_modules/.bin/cleancss
DEPLOY_DIR = libs
LIBJITSIMEET_DIR = node_modules/lib-jitsi-meet/
LIBFLAC_DIR = node_modules/libflacjs/dist/min/
RNNOISE_WASM_DIR = node_modules/rnnoise-wasm/dist/
NODE_SASS = ./node_modules/.bin/node-sass
NPM = npm
OUTPUT_DIR = .
@@ -21,7 +20,7 @@ compile:
clean:
rm -fr $(BUILD_DIR)
deploy: deploy-init deploy-appbundle deploy-rnnoise-binary deploy-lib-jitsi-meet deploy-libflac deploy-css deploy-local
deploy: deploy-init deploy-appbundle deploy-lib-jitsi-meet deploy-libflac deploy-css deploy-local
deploy-init:
rm -fr $(DEPLOY_DIR)
@@ -46,10 +45,6 @@ deploy-appbundle:
$(OUTPUT_DIR)/analytics-ga.js \
$(BUILD_DIR)/analytics-ga.min.js \
$(BUILD_DIR)/analytics-ga.min.map \
$(BUILD_DIR)/video-blur-effect.min.js \
$(BUILD_DIR)/video-blur-effect.min.map \
$(BUILD_DIR)/rnnoise-processor.min.js \
$(BUILD_DIR)/rnnoise-processor.min.map \
$(DEPLOY_DIR)
deploy-lib-jitsi-meet:
@@ -66,11 +61,6 @@ deploy-libflac:
$(LIBFLAC_DIR)/libflac4-1.3.2.min.js.mem \
$(DEPLOY_DIR)
deploy-rnnoise-binary:
cp \
$(RNNOISE_WASM_DIR)/rnnoise.wasm \
$(DEPLOY_DIR)
deploy-css:
$(NODE_SASS) $(STYLES_MAIN) $(STYLES_BUNDLE) && \
$(CLEANCSS) $(STYLES_BUNDLE) > $(STYLES_DESTINATION) ; \
@@ -79,7 +69,7 @@ deploy-css:
deploy-local:
([ ! -x deploy-local.sh ] || ./deploy-local.sh)
dev: deploy-init deploy-css deploy-rnnoise-binary deploy-lib-jitsi-meet deploy-libflac
dev: deploy-init deploy-css deploy-lib-jitsi-meet deploy-libflac
$(WEBPACK_DEV_SERVER)
source-package:

103
README.md
View File

@@ -2,7 +2,7 @@
Jitsi Meet is an open-source (Apache) WebRTC JavaScript application that uses [Jitsi Videobridge](https://jitsi.org/videobridge) to provide high quality, [secure](#security) and scalable video conferences. Jitsi Meet in action can be seen at [here at the session #482 of the VoIP Users Conference](http://youtu.be/7vFUVClsNh0).
The Jitsi Meet client runs in your browser, without installing anything else on your computer. You can try it out at https://meet.jit.si .
The Jitsi Meet client runs in your browser, without installing anything on your computer. You can try it out at https://meet.jit.si .
Jitsi Meet allows very efficient collaboration. Users can stream their desktop or only some windows. It also supports shared document editing with Etherpad.
@@ -27,28 +27,86 @@ You can download Debian/Ubuntu binaries:
You can download source archives (produced by ```make source-package```):
* [source builds](https://download.jitsi.org/jitsi-meet/src/)
### Mobile apps
You can get our mobile versions from here:
* [Android](https://play.google.com/store/apps/details?id=org.jitsi.meet)
[<img src="resources/img/google-play-badge.png" height="50">](https://play.google.com/store/apps/details?id=org.jitsi.meet)
* [Android (F-Droid)](https://f-droid.org/en/packages/org.jitsi.meet/)
[<img src="resources/img/f-droid-badge.png" height="50">](https://f-droid.org/en/packages/org.jitsi.meet/)
* [iOS](https://itunes.apple.com/us/app/jitsi-meet/id1165103905)
[<img src="resources/img/appstore-badge.png" height="50">](https://itunes.apple.com/us/app/jitsi-meet/id1165103905)
## Building the sources
You can also sign up for our open beta testing here:
Node.js >= 10 and npm >= 6 are required.
* [Android](https://play.google.com/apps/testing/org.jitsi.meet)
* [iOS](https://testflight.apple.com/join/isy6ja7S)
On Debian/Ubuntu systems, the required packages can be installed with:
```
sudo apt-get install npm nodejs
cd jitsi-meet
npm install
```
## Development
To build the Jitsi Meet application, just type
```
make
```
For web development see [here](doc/development.md), and for mobile see [here](doc/mobile.md).
### Working with the library sources (lib-jitsi-meet)
By default the library is build from its git repository sources. The default dependency path in package.json is :
```json
"lib-jitsi-meet": "jitsi/lib-jitsi-meet",
```
To work with local copy you must change the path to:
```json
"lib-jitsi-meet": "file:///Users/name/local-lib-jitsi-meet-copy",
```
To make the project you must force it to take the sources as 'npm update':
```
npm install lib-jitsi-meet --force && make
```
Or if you are making only changes to the library:
```
npm install lib-jitsi-meet --force && make deploy-lib-jitsi-meet
```
Alternative way is to use [npm link](https://docs.npmjs.com/cli/link).
It allows to link `lib-jitsi-meet` dependency to local source in few steps:
```bash
cd lib-jitsi-meet
#### create global symlink for lib-jitsi-meet package
npm link
cd ../jitsi-meet
#### create symlink from the local node_modules folder to the global lib-jitsi-meet symlink
npm link lib-jitsi-meet
```
After changes in local `lib-jitsi-meet` repository, you can rebuild it with `npm run install` and your `jitsi-meet` repository will use that modified library.
Note: when using node version 4.x, the make file of jitsi-meet do npm update which will delete the link. It is no longer the case with version 6.x.
If you do not want to use local repository anymore you should run
```bash
cd jitsi-meet
npm unlink lib-jitsi-meet
npm install
```
### Running with webpack-dev-server for development
Use it at the CLI, type
```
make dev
```
By default the backend deployment used is `beta.meet.jit.si`. You can point the Jitsi-Meet app at a different backend by using a proxy server. To do this, set the WEBPACK_DEV_SERVER_PROXY_TARGET variable:
```
export WEBPACK_DEV_SERVER_PROXY_TARGET=https://your-example-server.com
make dev
```
The app should be running at https://localhost:8080/
## Contributing
@@ -60,8 +118,7 @@ see our [guidelines for contributing](CONTRIBUTING.md).
Jitsi Meet provides a very flexible way of embedding in external applications by using the [Jitsi Meet API](doc/api.md).
## Security
WebRTC does not (yet) provide a way of conducting multi-party conversations with end-to-end encryption.
WebRTC does not provide a way of conducting multi-party conversations with end-to-end encryption.
Unless you consistently compare DTLS fingerprints with your peers vocally, the same goes for one-to-one calls.
As a result, your stream is encrypted on the network but decrypted on the machine that hosts the bridge when using Jitsi Meet.
@@ -73,13 +130,9 @@ Jitsi Meet in terms of security.
The [meet.jit.si](https://meet.jit.si) service is maintained by the Jitsi team
at [8x8](https://8x8.com).
## Security issues
We take security very seriously and develop all Jitsi projects to be secure and safe.
If you find (or simply suspect) a security issue in any of the Jitsi projects, please send us an email to security@jitsi.org.
**We encourage responsible disclosure for the sake of our users, so please reach out before posting in a public space.**
## Mobile app
Jitsi Meet is also available as a React Native app for Android and iOS.
Instructions on how to build it can be found [here](doc/mobile.md).
## Acknowledgements

View File

@@ -207,6 +207,24 @@ public class MainActivity extends FragmentActivity implements JitsiMeetActivityI
</details>
Starting with SDK version 1.22, a Glide module must be provided by the host app.
This makes it possible to use the Glide image processing library from both the
SDK and the host app itself.
You can use the code in `JitsiGlideModule.java` and adjust the package name.
When building, add the following code in your `app/build.gradle` file, adjusting
the Glide version to match the one in https://github.com/jitsi/jitsi-meet/blob/master/android/build.gradle
```
// Glide
implementation("com.github.bumptech.glide:glide:${glideVersion}") {
exclude group: "com.android.support", module: "glide"
}
implementation("com.github.bumptech.glide:annotations:${glideVersion}") {
exclude group: "com.android.support", module: "annotations"
}
```
### JitsiMeetActivity
This class encapsulates a high level API in the form of an Android `FragmentActivity`

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin/default"/>
</classpath>

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>app</name>
<comment>Project app created by Buildship.</comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures>
</projectDescription>

View File

@@ -1,2 +0,0 @@
connection.project.dir=..
eclipse.preferences.version=1

View File

@@ -32,23 +32,14 @@ android {
}
}
signingConfigs {
debug {
storeFile file('debug.keystore')
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}
}
buildTypes {
debug {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules-debug.pro'
buildConfigField "boolean", "GOOGLE_SERVICES_ENABLED", "${googleServicesEnabled}"
buildConfigField "boolean", "LIBRE_BUILD", "${rootProject.ext.libreBuild}"
}
release {
// Uncomment the following line for singing a test release build.
//signingConfig signingConfigs.debug
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules-release.pro'
buildConfigField "boolean", "GOOGLE_SERVICES_ENABLED", "${googleServicesEnabled}"
@@ -79,10 +70,8 @@ repositories {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-beta-5'
implementation "com.android.support:support-v4:${rootProject.ext.supportLibVersion}"
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
if (!rootProject.ext.libreBuild) {
implementation 'com.google.android.gms:play-services-auth:16.0.1'
@@ -96,6 +85,18 @@ dependencies {
}
implementation project(':sdk')
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
// Glide
implementation("com.github.bumptech.glide:glide:${rootProject.ext.glideVersion}") {
exclude group: "com.android.support", module: "glide"
}
implementation("com.github.bumptech.glide:annotations:${rootProject.ext.glideVersion}") {
exclude group: "com.android.support", module: "annotations"
}
annotationProcessor "com.github.bumptech.glide:compiler:${rootProject.ext.glideVersion}"
}
gradle.projectsEvaluated {

Binary file not shown.

View File

@@ -0,0 +1,5 @@
-include proguard-rules.pro
# Disabling obfuscation is useful if you collect stack traces from production crashes
# (unless you are using a system that supports de-obfuscate the stack traces).
-dontobfuscate

View File

@@ -9,6 +9,13 @@
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# React Native
# Keep our interfaces so they can be used by other ProGuard rules.
@@ -53,9 +60,19 @@
-keep class sun.misc.Unsafe { *; }
-dontwarn java.nio.file.*
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
-keep class okio.** { *; }
-dontwarn okio.**
# FastImage + Glide
-keep public class com.dylanvann.fastimage.* {*;}
-keep public class com.dylanvann.fastimage.** {*;}
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
# WebRTC
-keep class org.webrtc.** { *; }
@@ -83,10 +100,3 @@
-dontwarn javax.servlet.**
# ^^^ We added the above when we switched minifyEnabled on.
# Rule to avoid build errors related to SVGs.
-keep public class com.horcrux.svg.** {*;}
# Hermes
-keep class com.facebook.hermes.unicode.** { *; }

View File

@@ -5,6 +5,7 @@
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:name=".MainApplication"
android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/AppTheme">
<activity

View File

@@ -0,0 +1,16 @@
package org.jitsi.meet;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.module.AppGlideModule;
/**
* An AppGlideModule needs to be present for image loading events to work in
* react-native-fast-image. However, if this is defined by the SDK it will cause trouble with
* apps which are using Glide themselves.
*
* In order to avoid the problem, define a Jitsi Glide module here, so applications already using
* it are not in trouble.
*/
@GlideModule
public final class JitsiGlideModule extends AppGlideModule {
}

View File

@@ -1,5 +1,6 @@
/*
* Copyright @ 2017-present 8x8, Inc.
* Copyright @ 2018-present 8x8, Inc.
* Copyright @ 2017-2018 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,9 +21,9 @@ import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.KeyEvent;
import androidx.annotation.Nullable;
import org.jitsi.meet.sdk.JitsiMeet;
import org.jitsi.meet.sdk.JitsiMeetActivity;
@@ -90,7 +91,6 @@ public class MainActivity extends JitsiMeetActivity {
= new JitsiMeetConferenceOptions.Builder()
.setWelcomePageEnabled(true)
.setServerURL(buildURL("https://meet.jit.si"))
.setFeatureFlag("call-integration.enabled", false)
.build();
JitsiMeet.setDefaultConferenceOptions(defaultOptions);
@@ -106,7 +106,7 @@ public class MainActivity extends JitsiMeetActivity {
//
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == OVERLAY_PERMISSION_REQUEST_CODE
&& canRequestOverlayPermission()) {
if (Settings.canDrawOverlays(this)) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright @ 2019-present 8x8, Inc.
* Copyright @ 2018-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,14 +14,23 @@
* limitations under the License.
*/
#import <Foundation/Foundation.h>
package org.jitsi.meet;
#import "JitsiMeetBaseLogHandler.h"
import android.app.Application;
import com.squareup.leakcanary.LeakCanary;
@interface JitsiMeetLogger : NSObject
/**
* Simple {@link Application} for hooking up LeakCanary:
* https://github.com/square/leakcanary
*/
public class MainApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
+ (void)addHandler:(JitsiMeetBaseLogHandler *)handler;
+ (void)removeHandler:(JitsiMeetBaseLogHandler *)handler;
@end
if (!LeakCanary.isInAnalyzerProcess(this)) {
LeakCanary.install(this);
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 659 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 379 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 960 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#66A8DD</color>
</resources>

View File

@@ -13,8 +13,8 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.2'
classpath 'com.google.gms:google-services:4.3.3'
classpath 'io.fabric.tools:gradle:1.28.1'
classpath 'com.google.gms:google-services:4.2.0'
classpath 'io.fabric.tools:gradle:1.27.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files.
@@ -25,7 +25,8 @@ allprojects {
repositories {
google()
jcenter()
// React Native (JS, Obj-C sources, Android binaries) is installed from npm.
// React Native (JS, Obj-C sources, Android binaries) is installed from
// npm.
maven { url "$rootDir/../node_modules/react-native/android" }
}
@@ -73,13 +74,6 @@ allprojects {
def versionQualifierNumber = (int)(((new Date().getTime()/1000) - 1546297200) / 10)
afterEvaluate { project ->
if (project.plugins.hasPlugin('android') || project.plugins.hasPlugin('android-library')) {
project.android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
}
}
if (project.name.startsWith('react-native-')) {
def npmManifest = project.file('../package.json')
def json = new JsonSlurper().parseText(npmManifest.text)
@@ -89,6 +83,17 @@ allprojects {
project.version = "${json.version}-jitsi-${versionQualifierNumber}"
project.android {
compileSdkVersion rootProject.ext.compileSdkVersion
if (rootProject.ext.has('buildToolsVersion')) {
buildToolsVersion rootProject.ext.buildToolsVersion
}
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
}
}
task androidSourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.source
@@ -159,8 +164,56 @@ ext {
mavenUser = System.env.MVN_USER ?: ""
mavenPassword = System.env.MVN_PASSWORD ?: ""
// Glide
excludeAppGlideModule = true
glideVersion = "4.7.1" // keep in sync with react-native-fast-image
// Libre build
libreBuild = (System.env.LIBRE_BUILD ?: "false").toBoolean()
libreBuild = (System.env.LIBRE_BUILD ?: "true").toBoolean()
}
// If Android SDK is not installed, accept its license so that it
// is automatically downloaded.
afterEvaluate { project ->
// Either the environment variable ANDROID_HOME or the property sdk.dir in
// local.properties identifies where Android SDK is installed.
def androidHome = System.env.ANDROID_HOME
if (!androidHome) {
// ANDROID_HOME is not set. Is sdk.dir set?
def file = file("${project.rootDir}/local.properties")
def props = new Properties()
if (file.canRead()) {
file.withInputStream {
props.load(it)
androidHome = props.'sdk.dir'
}
}
if (!androidHome && (!file.exists() || file.canWrite())) {
// Neither ANDROID_HOME nor sdk.dir is set. Set sdk.dir (because
// environment variables cannot be set).
props.'sdk.dir' = "${project.buildDir}/android-sdk".toString()
file.withOutputStream {
props.store(it, null)
androidHome = props.'sdk.dir'
}
}
}
// If the license is not accepted, accept it so that automatic downloading
// kicks in.
// The license hash can be taken from the accepted licenses, by doing this
// on your local machine the file is
// ${androidHome}/licenses/android-sdk-license
if (androidHome) {
def dir = file("${androidHome}/licenses")
dir.mkdirs()
def file = file("${dir.path}/android-sdk-license")
if (!file.exists()) {
file.withWriter {
def hash = 'd56f5187479451eabf01fb78af6dfcb131a6481e'
it.write(hash, 0, hash.length())
}
}
}
}
// Force the version of the Android build tools we have chosen on all

View File

@@ -24,7 +24,7 @@ platform :android do
# Upload built artifact to the Closed Beta track
upload_to_play_store(
track: "beta",
track: "Closed Beta",
json_key: ENV["JITSI_JSON_KEY_FILE"],
skip_upload_metadata: true,
skip_upload_images: true,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 694 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 716 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 950 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1000 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

View File

@@ -17,8 +17,5 @@
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
android.useAndroidX=true
android.enableJetifier=true
appVersion=20.1.0
sdkVersion=2.6.0
appVersion=19.2.0
sdkVersion=2.1.0

View File

@@ -9,8 +9,7 @@ THE_MVN_REPO=${MVN_REPO:-${1:-$DEFAULT_MVN_REPO}}
MVN_HTTP=0
DEFAULT_SDK_VERSION=$(grep sdkVersion ${THIS_DIR}/../gradle.properties | cut -d"=" -f2)
SDK_VERSION=${OVERRIDE_SDK_VERSION:-${DEFAULT_SDK_VERSION}}
RN_VERSION=$(jq -r '.version' ${THIS_DIR}/../../node_modules/react-native/package.json)
HERMES_VERSION=$(jq -r '.dependencies."hermes-engine"' ${THIS_DIR}/../../node_modules/react-native/package.json | cut -c 2-)
RN_VERSION=$(jq -r '.dependencies."react-native"' ${THIS_DIR}/../../package.json)
DO_GIT_TAG=${GIT_TAG:-0}
if [[ $THE_MVN_REPO == http* ]]; then
@@ -38,22 +37,14 @@ if [[ $MVN_HTTP == 1 ]]; then
-DgeneratePom=false \
-DpomFile=react-native-${RN_VERSION}.pom || true
popd
# Push Hermes
echo "Pushing Hermes ${HERMES_VERSION} to the Maven repo"
pushd ${THIS_DIR}/../../node_modules/hermes-engine/android/
mvn \
deploy:deploy-file \
-Durl=${MVN_REPO} \
-DrepositoryId=${MVN_REPO_ID} \
-Dfile=hermes-release.aar \
-Dpackaging=aar \
-DgroupId=com.facebook \
-DartifactId=hermes \
-Dversion=${HERMES_VERSION} \
-DgeneratePom=true || true
popd
else
# Push React Native, if necessary
# Check if an SDK with that same version has already been released
if [[ -d ${MVN_REPO}/org/jitsi/react/jitsi-meet-sdk/${SDK_VERSION} ]]; then
echo "There is already a release with that version in the Maven repo!"
exit 1
fi
# First push React Native, if necessary
if [[ ! -d ${MVN_REPO}/com/facebook/react/react-native/${RN_VERSION} ]]; then
echo "Pushing React Native ${RN_VERSION} to the Maven repo"
pushd ${THIS_DIR}/../../node_modules/react-native/android/com/facebook/react/react-native/${RN_VERSION}
@@ -66,28 +57,6 @@ else
-DpomFile=react-native-${RN_VERSION}.pom
popd
fi
# Push Hermes, if necessary
if [[ ! -d ${MVN_REPO}/com/facebook/hermes/${HERMES_VERSION} ]]; then
echo "Pushing Hermes ${HERMES_VERSION} to the Maven repo"
pushd ${THIS_DIR}/../../node_modules/hermes-engine/android/
mvn \
deploy:deploy-file \
-Durl=${MVN_REPO} \
-Dfile=hermes-release.aar \
-Dpackaging=aar \
-DgroupId=com.facebook \
-DartifactId=hermes \
-Dversion=${HERMES_VERSION} \
-DgeneratePom=true
popd
fi
# Check if an SDK with that same version has already been released
if [[ -d ${MVN_REPO}/org/jitsi/react/jitsi-meet-sdk/${SDK_VERSION} ]]; then
echo "There is already a release with that version in the Maven repo!"
exit 1
fi
fi
# Now build and publish the Jitsi Meet SDK and its dependencies

View File

@@ -8,8 +8,6 @@ THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOUR
export RCT_METRO_PORT="${RCT_METRO_PORT:=8081}"
echo "export RCT_METRO_PORT=${RCT_METRO_PORT}" > "${THIS_DIR}/../../node_modules/react-native/scripts/.packager.env"
adb reverse tcp:8081 tcp:8081
if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then
if ! curl -s "http://localhost:${RCT_METRO_PORT}/status" | grep -q "packager-status:running" ; then
echo "Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly"

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin/default"/>
</classpath>

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>sdk</name>
<comment>Project sdk created by Buildship.</comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures>
</projectDescription>

View File

@@ -1,2 +0,0 @@
connection.project.dir=..
eclipse.preferences.version=1

View File

@@ -1,5 +1,3 @@
import groovy.json.JsonSlurper
apply plugin: 'com.android.library'
apply plugin: 'maven-publish'
@@ -33,48 +31,37 @@ android {
}
}
}
packagingOptions {
pickFirst '**/libc++_shared.so'
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.fragment:fragment:1.2.0'
implementation "com.android.support:support-v4:${rootProject.ext.supportLibVersion}"
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
//noinspection GradleDynamicVersion
api 'com.facebook.react:react-native:+'
// Hermes JS engine
def hermesPath = "../../node_modules/hermes-engine/android/"
debugImplementation files(hermesPath + "hermes-debug.aar")
releaseImplementation files(hermesPath + "hermes-release.aar")
implementation 'com.dropbox.core:dropbox-core-sdk:3.0.8'
implementation 'com.jakewharton.timber:timber:4.7.1'
implementation 'com.squareup.duktape:duktape-android:1.3.0'
if (!rootProject.ext.libreBuild) {
implementation 'com.amplitude:android-sdk:2.14.1'
implementation(project(":react-native-google-signin")) {
exclude group: 'com.google.android.gms'
exclude group: 'androidx'
exclude group: 'com.android.support'
}
}
implementation project(':react-native-background-timer')
implementation project(':react-native-calendar-events')
implementation project(':react-native-community-async-storage')
implementation project(':react-native-community_netinfo')
implementation(project(':react-native-fast-image')) {
exclude group: 'com.android.support'
}
implementation project(':react-native-immersive')
implementation project(':react-native-keep-awake')
implementation project(':react-native-linear-gradient')
implementation project(':react-native-sound')
implementation project(':react-native-svg')
implementation project(':react-native-vector-icons')
implementation project(':react-native-webrtc')
implementation project(':react-native-webview')
@@ -152,14 +139,19 @@ android.libraryVariants.all { def variant ->
mergeAssetsTask.doLast {
def assetsDir = mergeAssetsTask.outputDir
// Bundle fonts
//
copy {
from("${projectDir}/../../fonts/jitsi.ttf")
into("${assetsDir}/fonts")
}
// Bundle sounds
//
copy {
from("${projectDir}/../../sounds/incomingMessage.wav")
from("${projectDir}/../../sounds/joined.wav")
from("${projectDir}/../../sounds/left.wav")
from("${projectDir}/../../sounds/liveStreamingOn.mp3")
from("${projectDir}/../../sounds/liveStreamingOff.mp3")
from("${projectDir}/../../sounds/outgoingRinging.wav")
from("${projectDir}/../../sounds/outgoingStart.wav")
from("${projectDir}/../../sounds/recordingOn.mp3")
@@ -224,14 +216,6 @@ publishing {
dependency.appendNode('artifactId', artifactId)
dependency.appendNode('version', it.moduleVersion)
}
// Add Hermes dependency.
def hermesPkg = new File("$rootDir/../node_modules/hermes-engine/package.json")
def hermesVersion = new JsonSlurper().parseText(hermesPkg.text).version
def hermesDependency = dependencies.appendNode('dependency')
hermesDependency.appendNode('groupId', "com.facebook")
hermesDependency.appendNode('artifactId', "hermes")
hermesDependency.appendNode('version', hermesVersion)
}
}

View File

@@ -12,7 +12,6 @@
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-feature
android:glEsVersion="0x00020000"
@@ -45,8 +44,6 @@
<action android:name="android.telecom.ConnectionService" />
</intent-filter>
</service>
<service android:name="org.jitsi.meet.sdk.JitsiMeetOngoingConferenceService" />
</application>
</manifest>

View File

@@ -16,10 +16,6 @@
package org.jitsi.meet.sdk;
import android.annotation.SuppressLint;
import android.provider.Settings;
import android.text.TextUtils;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
@@ -28,7 +24,6 @@ import com.facebook.react.bridge.ReadableMap;
import com.amplitude.api.Amplitude;
import com.facebook.react.module.annotations.ReactModule;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import org.json.JSONException;
import org.json.JSONObject;
@@ -53,16 +48,8 @@ class AmplitudeModule
* @param apiKey The API_KEY of the Amplitude project.
*/
@ReactMethod
@SuppressLint("HardwareIds")
public void init(String instanceName, String apiKey) {
Amplitude.getInstance(instanceName).initialize(getCurrentActivity(), apiKey);
// Set the device ID to something consistent.
String android_id
= Settings.Secure.getString(getReactApplicationContext().getContentResolver(), Settings.Secure.ANDROID_ID);
if (!TextUtils.isEmpty(android_id)) {
Amplitude.getInstance(instanceName).setDeviceId(android_id);
}
}
/**
@@ -103,7 +90,7 @@ class AmplitudeModule
JSONObject eventProps = new JSONObject(eventPropsString);
Amplitude.getInstance(instanceName).logEvent(eventType, eventProps);
} catch (JSONException e) {
JitsiMeetLogger.e(e, "Error logging event");
e.printStackTrace();
}
}

View File

@@ -1,183 +0,0 @@
/*
* Copyright @ 2017-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.content.Context;
import android.media.AudioManager;
import android.os.Build;
import android.telecom.CallAudioState;
import androidx.annotation.RequiresApi;
import java.util.HashSet;
import java.util.Set;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
/**
* {@link AudioModeModule.AudioDeviceHandlerInterface} module implementing device handling for
* Android versions >= O when ConnectionService is enabled.
*/
@RequiresApi(Build.VERSION_CODES.O)
class AudioDeviceHandlerConnectionService implements
AudioModeModule.AudioDeviceHandlerInterface,
RNConnectionService.CallAudioStateListener {
private final static String TAG = AudioDeviceHandlerConnectionService.class.getSimpleName();
/**
* {@link AudioManager} instance used to interact with the Android audio subsystem.
*/
private AudioManager audioManager;
/**
* Reference to the main {@code AudioModeModule}.
*/
private AudioModeModule module;
/**
* Converts any of the "DEVICE_" constants into the corresponding
* {@link android.telecom.CallAudioState} "ROUTE_" number.
*
* @param audioDevice one of the "DEVICE_" constants.
* @return a route number {@link android.telecom.CallAudioState#ROUTE_EARPIECE} if
* no match is found.
*/
private static int audioDeviceToRouteInt(String audioDevice) {
if (audioDevice == null) {
return CallAudioState.ROUTE_SPEAKER;
}
switch (audioDevice) {
case AudioModeModule.DEVICE_BLUETOOTH:
return CallAudioState.ROUTE_BLUETOOTH;
case AudioModeModule.DEVICE_EARPIECE:
return CallAudioState.ROUTE_EARPIECE;
case AudioModeModule.DEVICE_HEADPHONES:
return CallAudioState.ROUTE_WIRED_HEADSET;
case AudioModeModule.DEVICE_SPEAKER:
return CallAudioState.ROUTE_SPEAKER;
default:
JitsiMeetLogger.e(TAG + " Unsupported device name: " + audioDevice);
return CallAudioState.ROUTE_SPEAKER;
}
}
/**
* Populates given route mask into the "DEVICE_" list.
*
* @param supportedRouteMask an integer coming from
* {@link android.telecom.CallAudioState#getSupportedRouteMask()}.
* @return a list of device names.
*/
private static Set<String> routesToDeviceNames(int supportedRouteMask) {
Set<String> devices = new HashSet<>();
if ((supportedRouteMask & CallAudioState.ROUTE_EARPIECE) == CallAudioState.ROUTE_EARPIECE) {
devices.add(AudioModeModule.DEVICE_EARPIECE);
}
if ((supportedRouteMask & CallAudioState.ROUTE_BLUETOOTH) == CallAudioState.ROUTE_BLUETOOTH) {
devices.add(AudioModeModule.DEVICE_BLUETOOTH);
}
if ((supportedRouteMask & CallAudioState.ROUTE_SPEAKER) == CallAudioState.ROUTE_SPEAKER) {
devices.add(AudioModeModule.DEVICE_SPEAKER);
}
if ((supportedRouteMask & CallAudioState.ROUTE_WIRED_HEADSET) == CallAudioState.ROUTE_WIRED_HEADSET) {
devices.add(AudioModeModule.DEVICE_HEADPHONES);
}
return devices;
}
/**
* Used to store the most recently reported audio devices.
* Makes it easier to compare for a change, because the devices are stored
* as a mask in the {@link android.telecom.CallAudioState}. The mask is populated into
* the {@code availableDevices} on each update.
*/
private int supportedRouteMask = -1;
public AudioDeviceHandlerConnectionService(AudioManager audioManager) {
this.audioManager = audioManager;
}
@Override
public void onCallAudioStateChange(final CallAudioState state) {
module.runInAudioThread(new Runnable() {
@Override
public void run() {
boolean audioRouteChanged
= audioDeviceToRouteInt(module.getSelectedDevice()) != state.getRoute();
int newSupportedRoutes = state.getSupportedRouteMask();
boolean audioDevicesChanged = supportedRouteMask != newSupportedRoutes;
if (audioDevicesChanged) {
supportedRouteMask = newSupportedRoutes;
Set<String> devices = routesToDeviceNames(supportedRouteMask);
module.replaceDevices(devices);
JitsiMeetLogger.i(TAG + " Available audio devices: " + devices.toString());
}
if (audioRouteChanged || audioDevicesChanged) {
module.resetSelectedDevice();
module.updateAudioRoute();
}
}
});
}
@Override
public void start(AudioModeModule audioModeModule) {
JitsiMeetLogger.i("Using " + TAG + " as the audio device handler");
module = audioModeModule;
RNConnectionService rcs = ReactInstanceManagerHolder.getNativeModule(RNConnectionService.class);
if (rcs != null) {
rcs.setCallAudioStateListener(this);
} else {
JitsiMeetLogger.w(TAG + " Couldn't set call audio state listener, module is null");
}
}
@Override
public void stop() {
RNConnectionService rcs = ReactInstanceManagerHolder.getNativeModule(RNConnectionService.class);
if (rcs != null) {
rcs.setCallAudioStateListener(null);
} else {
JitsiMeetLogger.w(TAG + " Couldn't set call audio state listener, module is null");
}
}
public void setAudioRoute(String audioDevice) {
int newAudioRoute = audioDeviceToRouteInt(audioDevice);
RNConnectionService.setAudioRoute(newAudioRoute);
}
@Override
public boolean setMode(int mode) {
if (mode != AudioModeModule.DEFAULT) {
// This shouldn't be needed when using ConnectionService, but some devices have been
// observed not doing it.
try {
audioManager.setMicrophoneMute(false);
} catch (Throwable tr) {
JitsiMeetLogger.w(tr, TAG + " Failed to unmute the microphone");
}
}
return true;
}
}

View File

@@ -1,231 +0,0 @@
/*
* Copyright @ 2017-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.content.Context;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.os.Build;
import androidx.annotation.RequiresApi;
import java.util.HashSet;
import java.util.Set;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
/**
* {@link AudioModeModule.AudioDeviceHandlerInterface} module implementing device handling for
* all post-M Android versions. This handler can be used on any Android versions >= M, but by
* default it's only used on versions < O, since versions >= O use ConnectionService, but it
* can be disabled.
*/
@RequiresApi(Build.VERSION_CODES.M)
class AudioDeviceHandlerGeneric implements
AudioModeModule.AudioDeviceHandlerInterface,
AudioManager.OnAudioFocusChangeListener {
private final static String TAG = AudioDeviceHandlerGeneric.class.getSimpleName();
/**
* Reference to the main {@code AudioModeModule}.
*/
private AudioModeModule module;
/**
* Constant defining a USB headset. Only available on API level >= 26.
* The value of: AudioDeviceInfo.TYPE_USB_HEADSET
*/
private static final int TYPE_USB_HEADSET = 22;
/**
* Indicator that we have lost audio focus.
*/
private boolean audioFocusLost = false;
/**
* {@link AudioManager} instance used to interact with the Android audio
* subsystem.
*/
private AudioManager audioManager;
/**
* {@link Runnable} for running audio device detection the main thread.
* This is only used on Android >= M.
*/
private final Runnable onAudioDeviceChangeRunner = new Runnable() {
@Override
public void run() {
Set<String> devices = new HashSet<>();
AudioDeviceInfo[] deviceInfos = audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
for (AudioDeviceInfo info: deviceInfos) {
switch (info.getType()) {
case AudioDeviceInfo.TYPE_BLUETOOTH_SCO:
devices.add(AudioModeModule.DEVICE_BLUETOOTH);
break;
case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
devices.add(AudioModeModule.DEVICE_EARPIECE);
break;
case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
devices.add(AudioModeModule.DEVICE_SPEAKER);
break;
case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
case AudioDeviceInfo.TYPE_WIRED_HEADSET:
case TYPE_USB_HEADSET:
devices.add(AudioModeModule.DEVICE_HEADPHONES);
break;
}
}
module.replaceDevices(devices);
JitsiMeetLogger.i(TAG + " Available audio devices: " + devices.toString());
module.updateAudioRoute();
}
};
private final android.media.AudioDeviceCallback audioDeviceCallback =
new android.media.AudioDeviceCallback() {
@Override
public void onAudioDevicesAdded(
AudioDeviceInfo[] addedDevices) {
JitsiMeetLogger.d(TAG + " Audio devices added");
onAudioDeviceChange();
}
@Override
public void onAudioDevicesRemoved(
AudioDeviceInfo[] removedDevices) {
JitsiMeetLogger.d(TAG + " Audio devices removed");
onAudioDeviceChange();
}
};
public AudioDeviceHandlerGeneric(AudioManager audioManager) {
this.audioManager = audioManager;
}
/**
* Helper method to trigger an audio route update when devices change. It
* makes sure the operation is performed on the audio thread.
*/
private void onAudioDeviceChange() {
module.runInAudioThread(onAudioDeviceChangeRunner);
}
/**
* {@link AudioManager.OnAudioFocusChangeListener} interface method. Called
* when the audio focus of the system is updated.
*
* @param focusChange - The type of focus change.
*/
@Override
public void onAudioFocusChange(final int focusChange) {
module.runInAudioThread(new Runnable() {
@Override
public void run() {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN: {
JitsiMeetLogger.d(TAG + " Audio focus gained");
// Some other application potentially stole our audio focus
// temporarily. Restore our mode.
if (audioFocusLost) {
module.updateAudioRoute();
}
audioFocusLost = false;
break;
}
case AudioManager.AUDIOFOCUS_LOSS:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: {
JitsiMeetLogger.d(TAG + " Audio focus lost");
audioFocusLost = true;
break;
}
}
}
});
}
/**
* Helper method to set the output route to a Bluetooth device.
*
* @param enabled true if Bluetooth should use used, false otherwise.
*/
private void setBluetoothAudioRoute(boolean enabled) {
if (enabled) {
audioManager.startBluetoothSco();
audioManager.setBluetoothScoOn(true);
} else {
audioManager.setBluetoothScoOn(false);
audioManager.stopBluetoothSco();
}
}
@Override
public void start(AudioModeModule audioModeModule) {
JitsiMeetLogger.i("Using " + TAG + " as the audio device handler");
module = audioModeModule;
// Setup runtime device change detection.
audioManager.registerAudioDeviceCallback(audioDeviceCallback, null);
// Do an initial detection.
onAudioDeviceChange();
}
@Override
public void stop() {
audioManager.unregisterAudioDeviceCallback(audioDeviceCallback);
}
@Override
public void setAudioRoute(String device) {
// Turn speaker on / off
audioManager.setSpeakerphoneOn(device.equals(AudioModeModule.DEVICE_SPEAKER));
// Turn bluetooth on / off
setBluetoothAudioRoute(device.equals(AudioModeModule.DEVICE_BLUETOOTH));
}
@Override
public boolean setMode(int mode) {
if (mode == AudioModeModule.DEFAULT) {
audioFocusLost = false;
audioManager.setMode(AudioManager.MODE_NORMAL);
audioManager.abandonAudioFocus(this);
audioManager.setSpeakerphoneOn(false);
setBluetoothAudioRoute(false);
return true;
}
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
audioManager.setMicrophoneMute(false);
if (audioManager.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN)
== AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
JitsiMeetLogger.w(TAG + " Audio focus request failed");
return false;
}
return true;
}
}

View File

@@ -1,230 +0,0 @@
/*
* Copyright @ 2017-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
/**
* {@link AudioModeModule.AudioDeviceHandlerInterface} module implementing device handling for
* legacy (pre-M) Android versions.
*/
class AudioDeviceHandlerLegacy implements
AudioModeModule.AudioDeviceHandlerInterface,
AudioManager.OnAudioFocusChangeListener,
BluetoothHeadsetMonitor.Listener {
private final static String TAG = AudioDeviceHandlerLegacy.class.getSimpleName();
/**
* Reference to the main {@code AudioModeModule}.
*/
private AudioModeModule module;
/**
* Indicator that we have lost audio focus.
*/
private boolean audioFocusLost = false;
/**
* {@link AudioManager} instance used to interact with the Android audio
* subsystem.
*/
private AudioManager audioManager;
/**
* {@link BluetoothHeadsetMonitor} for detecting Bluetooth device changes in
* old (< M) Android versions.
*/
private BluetoothHeadsetMonitor bluetoothHeadsetMonitor;
public AudioDeviceHandlerLegacy(AudioManager audioManager) {
this.audioManager = audioManager;
}
/**
* Helper method to trigger an audio route update when Bluetooth devices are
* connected / disconnected.
*/
@Override
public void onBluetoothDeviceChange(final boolean deviceAvailable) {
module.runInAudioThread(new Runnable() {
@Override
public void run() {
if (deviceAvailable) {
module.addDevice(AudioModeModule.DEVICE_BLUETOOTH);
} else {
module.removeDevice(AudioModeModule.DEVICE_BLUETOOTH);
}
module.updateAudioRoute();
}
});
}
/**
* Helper method to trigger an audio route update when a headset is plugged
* or unplugged.
*/
private void onHeadsetDeviceChange() {
module.runInAudioThread(new Runnable() {
@Override
public void run() {
// XXX: isWiredHeadsetOn is not deprecated when used just for
// knowing if there is a wired headset connected, regardless of
// audio being routed to it.
//noinspection deprecation
if (audioManager.isWiredHeadsetOn()) {
module.addDevice(AudioModeModule.DEVICE_HEADPHONES);
} else {
module.removeDevice(AudioModeModule.DEVICE_HEADPHONES);
}
module.updateAudioRoute();
}
});
}
/**
* {@link AudioManager.OnAudioFocusChangeListener} interface method. Called
* when the audio focus of the system is updated.
*
* @param focusChange - The type of focus change.
*/
@Override
public void onAudioFocusChange(final int focusChange) {
module.runInAudioThread(new Runnable() {
@Override
public void run() {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN: {
JitsiMeetLogger.d(TAG + " Audio focus gained");
// Some other application potentially stole our audio focus
// temporarily. Restore our mode.
if (audioFocusLost) {
module.updateAudioRoute();
}
audioFocusLost = false;
break;
}
case AudioManager.AUDIOFOCUS_LOSS:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: {
JitsiMeetLogger.d(TAG + " Audio focus lost");
audioFocusLost = true;
break;
}
}
}
});
}
/**
* Helper method to set the output route to a Bluetooth device.
*
* @param enabled true if Bluetooth should use used, false otherwise.
*/
private void setBluetoothAudioRoute(boolean enabled) {
if (enabled) {
audioManager.startBluetoothSco();
audioManager.setBluetoothScoOn(true);
} else {
audioManager.setBluetoothScoOn(false);
audioManager.stopBluetoothSco();
}
}
@Override
public void start(AudioModeModule audioModeModule) {
JitsiMeetLogger.i("Using " + TAG + " as the audio device handler");
module = audioModeModule;
Context context = module.getContext();
// Setup runtime device change detection.
//
// Detect changes in wired headset connections.
IntentFilter wiredHeadSetFilter = new IntentFilter(AudioManager.ACTION_HEADSET_PLUG);
BroadcastReceiver wiredHeadsetReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
JitsiMeetLogger.d(TAG + " Wired headset added / removed");
onHeadsetDeviceChange();
}
};
context.registerReceiver(wiredHeadsetReceiver, wiredHeadSetFilter);
// Detect Bluetooth device changes.
bluetoothHeadsetMonitor = new BluetoothHeadsetMonitor(context, this);
// On Android < M, detect if we have an earpiece.
PackageManager pm = context.getPackageManager();
if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
module.addDevice(AudioModeModule.DEVICE_EARPIECE);
}
// Always assume there is a speaker.
module.addDevice(AudioModeModule.DEVICE_SPEAKER);
}
@Override
public void stop() {
bluetoothHeadsetMonitor.stop();
bluetoothHeadsetMonitor = null;
}
@Override
public void setAudioRoute(String device) {
// Turn speaker on / off
audioManager.setSpeakerphoneOn(device.equals(AudioModeModule.DEVICE_SPEAKER));
// Turn bluetooth on / off
setBluetoothAudioRoute(device.equals(AudioModeModule.DEVICE_BLUETOOTH));
}
@Override
public boolean setMode(int mode) {
if (mode == AudioModeModule.DEFAULT) {
audioFocusLost = false;
audioManager.setMode(AudioManager.MODE_NORMAL);
audioManager.abandonAudioFocus(this);
audioManager.setSpeakerphoneOn(false);
setBluetoothAudioRoute(false);
return true;
}
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
audioManager.setMicrophoneMute(false);
if (audioManager.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN)
== AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
JitsiMeetLogger.w(TAG + " Audio focus request failed");
return false;
}
return true;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright @ 2017-present 8x8, Inc.
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,9 +16,17 @@
package org.jitsi.meet.sdk;
import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.Log;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
@@ -29,8 +37,6 @@ import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.module.annotations.ReactModule;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -54,7 +60,9 @@ import java.util.concurrent.Executors;
* {@code AudioModeModule.DEFAULT} mode should be used.
*/
@ReactModule(name = AudioModeModule.NAME)
class AudioModeModule extends ReactContextBaseJavaModule {
class AudioModeModule extends ReactContextBaseJavaModule
implements AudioManager.OnAudioFocusChangeListener {
public static final String NAME = "AudioMode";
/**
@@ -66,39 +74,174 @@ class AudioModeModule extends ReactContextBaseJavaModule {
* - VIDEO_CALL: Used for video calls. It will use the speaker by default,
* unless a wired or Bluetooth headset is connected.
*/
static final int DEFAULT = 0;
static final int AUDIO_CALL = 1;
static final int VIDEO_CALL = 2;
private static final int DEFAULT = 0;
private static final int AUDIO_CALL = 1;
private static final int VIDEO_CALL = 2;
/**
* Constant defining the action for plugging in a headset. This is used on
* our device detection system for API < 23.
*/
private static final String ACTION_HEADSET_PLUG
= (Build.VERSION.SDK_INT >= 21)
? AudioManager.ACTION_HEADSET_PLUG
: Intent.ACTION_HEADSET_PLUG;
/**
* Constant defining a USB headset. Only available on API level >= 26.
* The value of: AudioDeviceInfo.TYPE_USB_HEADSET
*/
private static final int TYPE_USB_HEADSET = 22;
/**
* The {@code Log} tag {@code AudioModeModule} is to log messages with.
*/
static final String TAG = NAME;
/**
* Converts any of the "DEVICE_" constants into the corresponding
* {@link android.telecom.CallAudioState} "ROUTE_" number.
*
* @param audioDevice one of the "DEVICE_" constants.
* @return a route number {@link android.telecom.CallAudioState#ROUTE_EARPIECE} if
* no match is found.
*/
@RequiresApi(api = Build.VERSION_CODES.M)
private static int audioDeviceToRouteInt(String audioDevice) {
if (audioDevice == null) {
return android.telecom.CallAudioState.ROUTE_EARPIECE;
}
switch (audioDevice) {
case DEVICE_BLUETOOTH:
return android.telecom.CallAudioState.ROUTE_BLUETOOTH;
case DEVICE_EARPIECE:
return android.telecom.CallAudioState.ROUTE_EARPIECE;
case DEVICE_HEADPHONES:
return android.telecom.CallAudioState.ROUTE_WIRED_HEADSET;
case DEVICE_SPEAKER:
return android.telecom.CallAudioState.ROUTE_SPEAKER;
default:
Log.e(TAG, "Unsupported device name: " + audioDevice);
return android.telecom.CallAudioState.ROUTE_EARPIECE;
}
}
/**
* Populates given route mask into the "DEVICE_" list.
*
* @param supportedRouteMask an integer coming from
* {@link android.telecom.CallAudioState#getSupportedRouteMask()}.
* @return a list of device names.
*/
@RequiresApi(api = Build.VERSION_CODES.M)
private static Set<String> routesToDeviceNames(int supportedRouteMask) {
Set<String> devices = new HashSet<>();
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_EARPIECE)
== android.telecom.CallAudioState.ROUTE_EARPIECE) {
devices.add(DEVICE_EARPIECE);
}
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_BLUETOOTH)
== android.telecom.CallAudioState.ROUTE_BLUETOOTH) {
devices.add(DEVICE_BLUETOOTH);
}
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_SPEAKER)
== android.telecom.CallAudioState.ROUTE_SPEAKER) {
devices.add(DEVICE_SPEAKER);
}
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_WIRED_HEADSET)
== android.telecom.CallAudioState.ROUTE_WIRED_HEADSET) {
devices.add(DEVICE_HEADPHONES);
}
return devices;
}
/**
* Whether or not the ConnectionService is used for selecting audio devices.
*/
private static final boolean supportsConnectionService = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
private static boolean useConnectionService_ = supportsConnectionService;
static boolean useConnectionService() {
return supportsConnectionService && useConnectionService_;
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
}
/**
* Indicator that we have lost audio focus.
*/
private boolean audioFocusLost = false;
/**
* {@link AudioManager} instance used to interact with the Android audio
* subsystem.
*/
private AudioManager audioManager;
private final AudioManager audioManager;
private AudioDeviceHandlerInterface audioDeviceHandler;
/**
* {@link BluetoothHeadsetMonitor} for detecting Bluetooth device changes in
* old (< M) Android versions.
*/
private BluetoothHeadsetMonitor bluetoothHeadsetMonitor;
/**
* {@link ExecutorService} for running all audio operations on a dedicated
* thread.
*/
private static final ExecutorService executor = Executors.newSingleThreadExecutor();
private static final ExecutorService executor
= Executors.newSingleThreadExecutor();
/**
* {@link Runnable} for running audio device detection the main thread.
* This is only used on Android >= M.
*/
private final Runnable onAudioDeviceChangeRunner = new Runnable() {
@TargetApi(Build.VERSION_CODES.M)
@Override
public void run() {
Set<String> devices = new HashSet<>();
AudioDeviceInfo[] deviceInfos
= audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
for (AudioDeviceInfo info: deviceInfos) {
switch (info.getType()) {
case AudioDeviceInfo.TYPE_BLUETOOTH_SCO:
devices.add(DEVICE_BLUETOOTH);
break;
case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
devices.add(DEVICE_EARPIECE);
break;
case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
devices.add(DEVICE_SPEAKER);
break;
case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
case AudioDeviceInfo.TYPE_WIRED_HEADSET:
case TYPE_USB_HEADSET:
devices.add(DEVICE_HEADPHONES);
break;
}
}
availableDevices = devices;
Log.d(TAG, "Available audio devices: " +
availableDevices.toString());
// Reset user selection
userSelectedDevice = null;
if (mode != -1) {
updateAudioRoute(mode);
}
}
};
/**
* {@link Runnable} for running update operation on the main thread.
*/
private final Runnable updateAudioRouteRunner
= new Runnable() {
@Override
public void run() {
if (mode != -1) {
updateAudioRoute(mode);
}
}
};
/**
* Audio mode currently in use.
@@ -108,15 +251,10 @@ class AudioModeModule extends ReactContextBaseJavaModule {
/**
* Audio device types.
*/
static final String DEVICE_BLUETOOTH = "BLUETOOTH";
static final String DEVICE_EARPIECE = "EARPIECE";
static final String DEVICE_HEADPHONES = "HEADPHONES";
static final String DEVICE_SPEAKER = "SPEAKER";
/**
* Device change event.
*/
private static final String DEVICE_CHANGE_EVENT = "org.jitsi.meet:features/audio-mode#devices-update";
private static final String DEVICE_BLUETOOTH = "BLUETOOTH";
private static final String DEVICE_EARPIECE = "EARPIECE";
private static final String DEVICE_HEADPHONES = "HEADPHONES";
private static final String DEVICE_SPEAKER = "SPEAKER";
/**
* List of currently available audio devices.
@@ -128,6 +266,15 @@ class AudioModeModule extends ReactContextBaseJavaModule {
*/
private String selectedDevice;
/**
* Used on API >= 26 to store the most recently reported audio devices.
* Makes it easier to compare for a change, because the devices are stored
* as a mask in the {@link android.telecom.CallAudioState}. The mask is populated into
* the {@link #availableDevices} on each update.
*/
@RequiresApi(api = Build.VERSION_CODES.O)
private int supportedRouteMask;
/**
* User selected device. When null the default is used depending on the
* mode.
@@ -144,7 +291,30 @@ class AudioModeModule extends ReactContextBaseJavaModule {
public AudioModeModule(ReactApplicationContext reactContext) {
super(reactContext);
audioManager = (AudioManager)reactContext.getSystemService(Context.AUDIO_SERVICE);
audioManager
= (AudioManager)
reactContext.getSystemService(Context.AUDIO_SERVICE);
// Starting Oreo the ConnectionImpl from ConnectionService is used to
// detect the available devices.
if (!useConnectionService()) {
// Setup runtime device change detection.
setupAudioRouteChangeDetection();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Do an initial detection on Android >= M.
runInAudioThread(onAudioDeviceChangeRunner);
} else {
// On Android < M, detect if we have an earpiece.
PackageManager pm = reactContext.getPackageManager();
if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
availableDevices.add(DEVICE_EARPIECE);
}
// Always assume there is a speaker.
availableDevices.add(DEVICE_SPEAKER);
}
}
}
/**
@@ -157,7 +327,6 @@ class AudioModeModule extends ReactContextBaseJavaModule {
public Map<String, Object> getConstants() {
Map<String, Object> constants = new HashMap<>();
constants.put("DEVICE_CHANGE_EVENT", DEVICE_CHANGE_EVENT);
constants.put("AUDIO_CALL", AUDIO_CALL);
constants.put("DEFAULT", DEFAULT);
constants.put("VIDEO_CALL", VIDEO_CALL);
@@ -166,26 +335,31 @@ class AudioModeModule extends ReactContextBaseJavaModule {
}
/**
* Notifies JS land that the devices list has changed.
* Gets the list of available audio device categories, i.e. 'bluetooth',
* 'earpiece ', 'speaker', 'headphones'.
*
* @param promise a {@link Promise} which will be resolved with an object
* containing a 'devices' key with a list of devices, plus a
* 'selected' key with the selected one.
*/
private void notifyDevicesChanged() {
@ReactMethod
public void getAudioDevices(final Promise promise) {
runInAudioThread(new Runnable() {
@Override
public void run() {
WritableArray data = Arguments.createArray();
final boolean hasHeadphones = availableDevices.contains(DEVICE_HEADPHONES);
WritableMap map = Arguments.createMap();
map.putString("selected", selectedDevice);
WritableArray devices = Arguments.createArray();
for (String device : availableDevices) {
if (hasHeadphones && device.equals(DEVICE_EARPIECE)) {
// Skip earpiece when headphones are plugged in.
if (mode == VIDEO_CALL && device.equals(DEVICE_EARPIECE)) {
// Skip earpiece when in video call mode.
continue;
}
WritableMap deviceInfo = Arguments.createMap();
deviceInfo.putString("type", device);
deviceInfo.putBoolean("selected", device.equals(selectedDevice));
data.pushMap(deviceInfo);
devices.pushString(device);
}
ReactInstanceManagerHolder.emitEvent(DEVICE_CHANGE_EVENT, data);
JitsiMeetLogger.i(TAG + " Updating audio device list");
map.putArray("devices", devices);
promise.resolve(map);
}
});
}
@@ -201,41 +375,133 @@ class AudioModeModule extends ReactContextBaseJavaModule {
}
/**
* Initializes the audio device handler module. This function is called *after* all Catalyst
* modules have been created, and that's why we use it, because {@link AudioDeviceHandlerConnectionService}
* needs access to another Catalyst module, so doing this in the constructor would be too early.
* Helper method to trigger an audio route update when devices change. It
* makes sure the operation is performed on the main thread.
*
* Only used on Android >= M.
*/
@Override
public void initialize() {
void onAudioDeviceChange() {
runInAudioThread(onAudioDeviceChangeRunner);
}
/**
* Helper method to trigger an audio route update when Bluetooth devices are
* connected / disconnected.
*
* Only used on Android < M. Runs on the main thread.
*/
void onBluetoothDeviceChange() {
if (bluetoothHeadsetMonitor != null && bluetoothHeadsetMonitor.isHeadsetAvailable()) {
availableDevices.add(DEVICE_BLUETOOTH);
} else {
availableDevices.remove(DEVICE_BLUETOOTH);
}
if (mode != -1) {
updateAudioRoute(mode);
}
}
/**
* Helper method to trigger an audio route update when a headset is plugged
* or unplugged.
*
* Only used on Android < M.
*/
void onHeadsetDeviceChange() {
runInAudioThread(new Runnable() {
@Override
public void run() {
setAudioDeviceHandler();
// XXX: isWiredHeadsetOn is not deprecated when used just for
// knowing if there is a wired headset connected, regardless of
// audio being routed to it.
//noinspection deprecation
if (audioManager.isWiredHeadsetOn()) {
availableDevices.add(DEVICE_HEADPHONES);
} else {
availableDevices.remove(DEVICE_HEADPHONES);
}
if (mode != -1) {
updateAudioRoute(mode);
}
}
});
}
private void setAudioDeviceHandler() {
if (audioDeviceHandler != null) {
audioDeviceHandler.stop();
@RequiresApi(api = Build.VERSION_CODES.O)
void onCallAudioStateChange(Object callAudioState_) {
final android.telecom.CallAudioState callAudioState
= (android.telecom.CallAudioState)callAudioState_;
runInAudioThread(new Runnable() {
@Override
public void run() {
int newSupportedRoutes = callAudioState.getSupportedRouteMask();
boolean audioDevicesChanged
= supportedRouteMask != newSupportedRoutes;
if (audioDevicesChanged) {
supportedRouteMask = newSupportedRoutes;
availableDevices = routesToDeviceNames(supportedRouteMask);
Log.d(TAG,
"Available audio devices: "
+ availableDevices.toString());
}
boolean audioRouteChanged
= audioDeviceToRouteInt(selectedDevice)
!= callAudioState.getRoute();
if (audioRouteChanged || audioDevicesChanged) {
// Reset user selection
userSelectedDevice = null;
// If the OS changes the Audio Route or Devices we could have lost
// the selected audio device
selectedDevice = null;
if (mode != -1) {
updateAudioRoute(mode);
}
}
}
});
}
/**
* {@link AudioManager.OnAudioFocusChangeListener} interface method. Called
* when the audio focus of the system is updated.
*
* @param focusChange - The type of focus change.
*/
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN: {
Log.d(TAG, "Audio focus gained");
// Some other application potentially stole our audio focus
// temporarily. Restore our mode.
if (audioFocusLost) {
updateAudioRoute(mode);
}
audioFocusLost = false;
break;
}
case AudioManager.AUDIOFOCUS_LOSS:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: {
Log.d(TAG, "Audio focus lost");
audioFocusLost = true;
break;
}
if (useConnectionService()) {
audioDeviceHandler = new AudioDeviceHandlerConnectionService(audioManager);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
audioDeviceHandler = new AudioDeviceHandlerGeneric(audioManager);
} else {
audioDeviceHandler = new AudioDeviceHandlerLegacy(audioManager);
}
audioDeviceHandler.start(this);
}
/**
* Helper function to run operations on a dedicated thread.
* @param runnable
*/
void runInAudioThread(Runnable runnable) {
public void runInAudioThread(Runnable runnable) {
executor.execute(runnable);
}
@@ -250,13 +516,13 @@ class AudioModeModule extends ReactContextBaseJavaModule {
@Override
public void run() {
if (!availableDevices.contains(device)) {
JitsiMeetLogger.w(TAG + " Audio device not available: " + device);
Log.d(TAG, "Audio device not available: " + device);
userSelectedDevice = null;
return;
}
if (mode != -1) {
JitsiMeetLogger.i(TAG + " User selected device set to: " + device);
Log.d(TAG, "User selected device set to: " + device);
userSelectedDevice = device;
updateAudioRoute(mode);
}
@@ -264,6 +530,46 @@ class AudioModeModule extends ReactContextBaseJavaModule {
});
}
/**
* The API >= 26 way of adjusting the audio route.
*
* @param audioDevice one of the "DEVICE_" names to set as the audio route.
*/
@RequiresApi(api = Build.VERSION_CODES.O)
private void setAudioRoute(String audioDevice) {
int newAudioRoute = audioDeviceToRouteInt(audioDevice);
RNConnectionService.setAudioRoute(newAudioRoute);
}
/**
* The API < 26 way of adjusting the audio route.
*
* @param audioDevice one of the "DEVICE_" names to set as the audio route.
*/
private void setAudioRoutePreO(String audioDevice) {
// Turn bluetooth on / off
setBluetoothAudioRoute(audioDevice.equals(DEVICE_BLUETOOTH));
// Turn speaker on / off
audioManager.setSpeakerphoneOn(audioDevice.equals(DEVICE_SPEAKER));
}
/**
* Helper method to set the output route to a Bluetooth device.
*
* @param enabled true if Bluetooth should use used, false otherwise.
*/
private void setBluetoothAudioRoute(boolean enabled) {
if (enabled) {
audioManager.startBluetoothSco();
audioManager.setBluetoothScoOn(true);
} else {
audioManager.setBluetoothScoOn(false);
audioManager.stopBluetoothSco();
}
}
/**
* Public method to set the current audio mode.
*
@@ -278,7 +584,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
return;
}
runInAudioThread(new Runnable() {
Runnable r = new Runnable() {
@Override
public void run() {
boolean success;
@@ -287,33 +593,80 @@ class AudioModeModule extends ReactContextBaseJavaModule {
success = updateAudioRoute(mode);
} catch (Throwable e) {
success = false;
JitsiMeetLogger.e(e, TAG + " Failed to update audio route for mode: " + mode);
Log.e(
TAG,
"Failed to update audio route for mode: " + mode,
e);
}
if (success) {
AudioModeModule.this.mode = mode;
promise.resolve(null);
} else {
promise.reject("setMode", "Failed to set audio mode to " + mode);
promise.reject(
"setMode",
"Failed to set audio mode to " + mode);
}
}
});
};
runInAudioThread(r);
}
/**
* Sets whether ConnectionService should be used (if available) for setting the audio mode
* or not.
*
* @param use Boolean indicator of where it should be used or not.
* Setup the audio route change detection mechanism. We use the
* {@link android.media.AudioDeviceCallback} on 23 >= Android API < 26.
*/
@ReactMethod
public void setUseConnectionService(final boolean use) {
runInAudioThread(new Runnable() {
private void setupAudioRouteChangeDetection() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
setupAudioRouteChangeDetectionM();
} else {
setupAudioRouteChangeDetectionPreM();
}
}
/**
* Audio route change detection mechanism for 23 >= Android API < 26.
*/
@TargetApi(Build.VERSION_CODES.M)
private void setupAudioRouteChangeDetectionM() {
android.media.AudioDeviceCallback audioDeviceCallback =
new android.media.AudioDeviceCallback() {
@Override
public void onAudioDevicesAdded(
AudioDeviceInfo[] addedDevices) {
Log.d(TAG, "Audio devices added");
onAudioDeviceChange();
}
@Override
public void onAudioDevicesRemoved(
AudioDeviceInfo[] removedDevices) {
Log.d(TAG, "Audio devices removed");
onAudioDeviceChange();
}
};
audioManager.registerAudioDeviceCallback(audioDeviceCallback, null);
}
/**
* Audio route change detection mechanism for Android API < 23.
*/
private void setupAudioRouteChangeDetectionPreM() {
Context context = getReactApplicationContext();
// Detect changes in wired headset connections.
IntentFilter wiredHeadSetFilter = new IntentFilter(ACTION_HEADSET_PLUG);
BroadcastReceiver wiredHeadsetReceiver = new BroadcastReceiver() {
@Override
public void run() {
useConnectionService_ = use;
setAudioDeviceHandler();
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Wired headset added / removed");
onHeadsetDeviceChange();
}
});
};
context.registerReceiver(wiredHeadsetReceiver, wiredHeadSetFilter);
// Detect Bluetooth device changes.
bluetoothHeadsetMonitor = new BluetoothHeadsetMonitor(this, context);
}
/**
@@ -324,21 +677,38 @@ class AudioModeModule extends ReactContextBaseJavaModule {
* {@code false}, otherwise.
*/
private boolean updateAudioRoute(int mode) {
JitsiMeetLogger.i(TAG + " Update audio route for mode: " + mode);
if (!audioDeviceHandler.setMode(mode)) {
return false;
}
Log.d(TAG, "Update audio route for mode: " + mode);
if (mode == DEFAULT) {
if (!useConnectionService()) {
audioFocusLost = false;
audioManager.setMode(AudioManager.MODE_NORMAL);
audioManager.abandonAudioFocus(this);
audioManager.setSpeakerphoneOn(false);
setBluetoothAudioRoute(false);
}
selectedDevice = null;
userSelectedDevice = null;
notifyDevicesChanged();
return true;
}
if (!useConnectionService()) {
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
audioManager.setMicrophoneMute(false);
if (audioManager.requestAudioFocus(
this,
AudioManager.STREAM_VOICE_CALL,
AudioManager.AUDIOFOCUS_GAIN)
== AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
Log.d(TAG, "Audio focus request failed");
return false;
}
}
boolean bluetoothAvailable = availableDevices.contains(DEVICE_BLUETOOTH);
boolean earpieceAvailable = availableDevices.contains(DEVICE_EARPIECE);
boolean headsetAvailable = availableDevices.contains(DEVICE_HEADPHONES);
// Pick the desired device based on what's available and the mode.
@@ -347,12 +717,15 @@ class AudioModeModule extends ReactContextBaseJavaModule {
audioDevice = DEVICE_BLUETOOTH;
} else if (headsetAvailable) {
audioDevice = DEVICE_HEADPHONES;
} else if (mode == AUDIO_CALL && earpieceAvailable) {
audioDevice = DEVICE_EARPIECE;
} else {
audioDevice = DEVICE_SPEAKER;
}
// Consider the user's selection
if (userSelectedDevice != null && availableDevices.contains(userSelectedDevice)) {
if (userSelectedDevice != null
&& availableDevices.contains(userSelectedDevice)) {
audioDevice = userSelectedDevice;
}
@@ -363,107 +736,14 @@ class AudioModeModule extends ReactContextBaseJavaModule {
}
selectedDevice = audioDevice;
JitsiMeetLogger.i(TAG + " Selected audio device: " + audioDevice);
Log.d(TAG, "Selected audio device: " + audioDevice);
audioDeviceHandler.setAudioRoute(audioDevice);
if (useConnectionService()) {
setAudioRoute(audioDevice);
} else {
setAudioRoutePreO(audioDevice);
}
notifyDevicesChanged();
return true;
}
/**
* Gets the currently selected audio device.
*
* @return The selected audio device.
*/
String getSelectedDevice() {
return selectedDevice;
}
/**
* Resets the current device selection.
*/
void resetSelectedDevice() {
selectedDevice = null;
userSelectedDevice = null;
}
/**
* Adds a new device to the list of available devices.
*
* @param device The new device.
*/
void addDevice(String device) {
availableDevices.add(device);
resetSelectedDevice();
}
/**
* Removes a device from the list of available devices.
*
* @param device The old device to the removed.
*/
void removeDevice(String device) {
availableDevices.remove(device);
resetSelectedDevice();
}
/**
* Replaces the current list of available devices with a new one.
*
* @param devices The new devices list.
*/
void replaceDevices(Set<String> devices) {
availableDevices = devices;
resetSelectedDevice();
}
/**
* Re-sets the current audio route. Needed when devices changes have happened.
*/
void updateAudioRoute() {
if (mode != -1) {
updateAudioRoute(mode);
}
}
/**
* Needed on the legacy handler...
*
* @return Context for the application.
*/
Context getContext() {
return getReactApplicationContext();
}
/**
* Interface for the modules implementing the actual audio device management.
*/
interface AudioDeviceHandlerInterface {
/**
* Start detecting audio device changes.
* @param audioModeModule Reference to the main {@link AudioModeModule}.
*/
void start(AudioModeModule audioModeModule);
/**
* Stop audio device detection.
*/
void stop();
/**
* Set the appropriate route for the given audio device.
*
* @param device Audio device for which the route must be set.
*/
void setAudioRoute(String device);
/**
* Set the given audio mode.
*
* @param mode The new audio mode to be used.
* @return Whether the operation was successful or not.
*/
boolean setMode(int mode);
}
}

View File

@@ -20,16 +20,15 @@ package org.jitsi.meet.sdk;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.ReactRootView;
import com.facebook.react.bridge.ReadableMap;
import com.rnimmersive.RNImmersiveModule;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
@@ -78,15 +77,6 @@ public abstract class BaseReactView<ListenerT>
return null;
}
/**
* Gets all registered React views.
*
* @return An {@link ArrayList} containing all views currently held by React.
*/
static ArrayList<BaseReactView> getViews() {
return new ArrayList<>(views);
}
/**
* The unique identifier of this {@code BaseReactView} within the process
* for the purposes of {@link ExternalAPIModule}. The name scope was
@@ -111,7 +101,8 @@ public abstract class BaseReactView<ListenerT>
setBackgroundColor(BACKGROUND_COLOR);
ReactInstanceManagerHolder.initReactInstanceManager((Activity)context);
ReactInstanceManagerHolder.initReactInstanceManager(
((Activity) context).getApplication());
// Hook this BaseReactView into ExternalAPI.
externalAPIScope = UUID.randomUUID().toString();

View File

@@ -1,5 +1,5 @@
/*
* Copyright @ 2017-present 8x8, Inc.
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,8 +24,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import android.util.Log;
/**
* Helper class to detect and handle Bluetooth device changes. It monitors
@@ -33,43 +32,68 @@ import org.jitsi.meet.sdk.log.JitsiMeetLogger;
* about device changes when this occurs.
*/
class BluetoothHeadsetMonitor {
private final static String TAG = BluetoothHeadsetMonitor.class.getSimpleName();
/**
* {@link AudioModeModule} where this monitor reports.
*/
private final AudioModeModule audioModeModule;
/**
* The {@link Context} in which this module executes.
* The {@link Context} in which {@link #audioModeModule} executes.
*/
private final Context context;
/**
* Reference to the {@link BluetoothAdapter} object, used to access Bluetooth functionality.
*/
private BluetoothAdapter adapter;
/**
* Reference to a proxy object which allows us to query connected devices.
*/
private BluetoothHeadset headset;
/**
* receiver registered for receiving Bluetooth connection state changes.
* Flag indicating if there are any Bluetooth headset devices currently
* available.
*/
private BroadcastReceiver receiver;
private boolean headsetAvailable = false;
/**
* Listener for receiving Bluetooth device change events.
* Helper for running Bluetooth operations on the main thread.
*/
private Listener listener;
private final Runnable updateDevicesRunnable
= new Runnable() {
@Override
public void run() {
headsetAvailable
= (headset != null)
&& !headset.getConnectedDevices().isEmpty();
audioModeModule.onBluetoothDeviceChange();
}
};
public BluetoothHeadsetMonitor(Context context, Listener listener) {
public BluetoothHeadsetMonitor(
AudioModeModule audioModeModule,
Context context) {
this.audioModeModule = audioModeModule;
this.context = context;
this.listener = listener;
AudioManager audioManager
= (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
if (!audioManager.isBluetoothScoAvailableOffCall()) {
Log.w(AudioModeModule.TAG, "Bluetooth SCO is not available");
return;
}
if (getBluetoothHeadsetProfileProxy()) {
registerBluetoothReceiver();
// Initial detection.
updateDevices();
}
}
private boolean getBluetoothHeadsetProfileProxy() {
adapter = BluetoothAdapter.getDefaultAdapter();
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter == null) {
JitsiMeetLogger.w(TAG + " Device doesn't support Bluetooth");
Log.w(AudioModeModule.TAG, "Device doesn't support Bluetooth");
return false;
}
@@ -79,7 +103,9 @@ class BluetoothHeadsetMonitor {
BluetoothProfile.ServiceListener listener
= new BluetoothProfile.ServiceListener() {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
public void onServiceConnected(
int profile,
BluetoothProfile proxy) {
if (profile == BluetoothProfile.HEADSET) {
headset = (BluetoothHeadset) proxy;
updateDevices();
@@ -93,7 +119,21 @@ class BluetoothHeadsetMonitor {
}
};
return adapter.getProfileProxy(context, listener, BluetoothProfile.HEADSET);
return
adapter.getProfileProxy(
context,
listener,
BluetoothProfile.HEADSET);
}
/**
* Returns the current headset availability.
*
* @return {@code true} if there is a Bluetooth headset connected;
* {@code false}, otherwise.
*/
public boolean isHeadsetAvailable() {
return headsetAvailable;
}
private void onBluetoothReceiverReceive(Context context, Intent intent) {
@@ -108,7 +148,9 @@ class BluetoothHeadsetMonitor {
switch (state) {
case BluetoothHeadset.STATE_CONNECTED:
case BluetoothHeadset.STATE_DISCONNECTED:
JitsiMeetLogger.d(TAG + " BT headset connection state changed: " + state);
Log.d(
AudioModeModule.TAG,
"BT headset connection state changed: " + state);
updateDevices();
break;
}
@@ -116,12 +158,15 @@ class BluetoothHeadsetMonitor {
// XXX: This action will be fired when the connection established
// with a Bluetooth headset (called a SCO connection) changes state.
// When the SCO connection is active we route audio to it.
int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -99);
int state
= intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -99);
switch (state) {
case AudioManager.SCO_AUDIO_STATE_CONNECTED:
case AudioManager.SCO_AUDIO_STATE_DISCONNECTED:
JitsiMeetLogger.d(TAG + " BT SCO connection state changed: " + state);
Log.d(
AudioModeModule.TAG,
"BT SCO connection state changed: " + state);
updateDevices();
break;
}
@@ -129,63 +174,24 @@ class BluetoothHeadsetMonitor {
}
private void registerBluetoothReceiver() {
receiver = new BroadcastReceiver() {
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
onBluetoothReceiverReceive(context, intent);
}
};
IntentFilter filter = new IntentFilter();
filter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
context.registerReceiver(receiver, filter);
}
/**
* Detects if there are new devices connected / disconnected and fires the
* {@link Listener} registered event.
* {@link AudioModeModule#onAudioDeviceChange()} callback.
*/
private void updateDevices() {
boolean headsetAvailable = (headset != null) && !headset.getConnectedDevices().isEmpty();
listener.onBluetoothDeviceChange(headsetAvailable);
}
public void start() {
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
if (!audioManager.isBluetoothScoAvailableOffCall()) {
JitsiMeetLogger.w(TAG + " Bluetooth SCO is not available");
return;
}
if (!getBluetoothHeadsetProfileProxy()) {
JitsiMeetLogger.w(TAG + " Couldn't get BT profile proxy");
return;
}
registerBluetoothReceiver();
// Initial detection.
updateDevices();
}
public void stop() {
if (receiver != null) {
context.unregisterReceiver(receiver);
}
if (adapter != null && headset != null) {
adapter.closeProfileProxy(BluetoothProfile.HEADSET, headset);
}
receiver = null;
headset = null;
adapter = null;
}
interface Listener {
void onBluetoothDeviceChange(boolean deviceAvailable);
audioModeModule.runInAudioThread(updateDevicesRunnable);
}
}

View File

@@ -5,6 +5,7 @@ import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.RequiresApi;
import android.telecom.CallAudioState;
import android.telecom.Connection;
import android.telecom.ConnectionRequest;
@@ -13,14 +14,12 @@ import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import androidx.annotation.RequiresApi;
import android.util.Log;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableNativeMap;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -64,16 +63,6 @@ public class ConnectionService extends android.telecom.ConnectionService {
static private final HashMap<String, Promise> startCallPromises
= new HashMap<>();
/**
* Aborts all ongoing connections. This is a last resort mechanism which forces all resources to
* be freed on the system in case of fatal error.
*/
static void abortConnections() {
for (ConnectionImpl connection: getConnections()) {
connection.onAbort();
}
}
/**
* Adds {@link ConnectionImpl} to the list.
*
@@ -123,17 +112,16 @@ public class ConnectionService extends android.telecom.ConnectionService {
* {@link android.telecom.Connection#STATE_ACTIVE}.
*
* @param callUUID the call UUID which identifies the connection.
* @return Whether the connection was set as active or not.
*/
static boolean setConnectionActive(String callUUID) {
static void setConnectionActive(String callUUID) {
ConnectionImpl connection = connections.get(callUUID);
if (connection != null) {
connection.setActive();
return true;
} else {
JitsiMeetLogger.w("%s setConnectionActive - no connection for UUID: %s", TAG, callUUID);
return false;
Log.e(TAG, String.format(
"setConnectionActive - no connection for UUID: %s",
callUUID));
}
}
@@ -164,7 +152,7 @@ public class ConnectionService extends android.telecom.ConnectionService {
connection.setDisconnected(cause);
connection.destroy();
} else {
JitsiMeetLogger.e(TAG + " endCall no connection for UUID: " + callUUID);
Log.e(TAG, "endCall no connection for UUID: " + callUUID);
}
}
@@ -196,14 +184,15 @@ public class ConnectionService extends android.telecom.ConnectionService {
boolean hasVideo
= callState.getBoolean(ConnectionImpl.KEY_HAS_VIDEO);
JitsiMeetLogger.i(" %s updateCall: %s hasVideo: %s", TAG, callUUID, hasVideo);
Log.d(TAG, String.format(
"updateCall: %s hasVideo: %s", callUUID, hasVideo));
connection.setVideoState(
hasVideo
? VideoProfile.STATE_BIDIRECTIONAL
: VideoProfile.STATE_AUDIO_ONLY);
}
} else {
JitsiMeetLogger.e(TAG + " updateCall no connection for UUID: " + callUUID);
Log.e(TAG, "updateCall no connection for UUID: " + callUUID);
}
}
@@ -239,11 +228,13 @@ public class ConnectionService extends android.telecom.ConnectionService {
= unregisterStartCallPromise(connection.getCallUUID());
if (startCallPromise != null) {
JitsiMeetLogger.d(TAG + " onCreateOutgoingConnection " + connection.getCallUUID());
Log.d(TAG,
"onCreateOutgoingConnection " + connection.getCallUUID());
startCallPromise.resolve(null);
} else {
JitsiMeetLogger.e(
TAG + " onCreateOutgoingConnection: no start call Promise for " + connection.getCallUUID());
Log.e(TAG, String.format(
"onCreateOutgoingConnection: no start call Promise for %s",
connection.getCallUUID()));
}
return connection;
@@ -267,7 +258,7 @@ public class ConnectionService extends android.telecom.ConnectionService {
PhoneAccountHandle theAccountHandle = request.getAccountHandle();
String callUUID = theAccountHandle.getId();
JitsiMeetLogger.e(TAG + " onCreateOutgoingConnectionFailed " + callUUID);
Log.e(TAG, "onCreateOutgoingConnectionFailed " + callUUID);
if (callUUID != null) {
Promise startCallPromise = unregisterStartCallPromise(callUUID);
@@ -277,10 +268,12 @@ public class ConnectionService extends android.telecom.ConnectionService {
"CREATE_OUTGOING_CALL_FAILED",
"The request has been denied by the system");
} else {
JitsiMeetLogger.e(TAG + " startCallFailed - no start call Promise for UUID: " + callUUID);
Log.e(TAG, String.format(
"startCallFailed - no start call Promise for UUID: %s",
callUUID));
}
} else {
JitsiMeetLogger.e(TAG + " onCreateOutgoingConnectionFailed - no call UUID");
Log.e(TAG, "onCreateOutgoingConnectionFailed - no call UUID");
}
unregisterPhoneAccount(theAccountHandle);
@@ -292,10 +285,10 @@ public class ConnectionService extends android.telecom.ConnectionService {
if (phoneAccountHandle != null) {
telecom.unregisterPhoneAccount(phoneAccountHandle);
} else {
JitsiMeetLogger.e(TAG + " unregisterPhoneAccount - account handle is null");
Log.e(TAG, "unregisterPhoneAccount - account handle is null");
}
} else {
JitsiMeetLogger.e(TAG + " unregisterPhoneAccount - telecom is null");
Log.e(TAG, "unregisterPhoneAccount - telecom is null");
}
}
@@ -354,7 +347,7 @@ public class ConnectionService extends android.telecom.ConnectionService {
*/
@Override
public void onDisconnect() {
JitsiMeetLogger.i(TAG + " onDisconnect " + getCallUUID());
Log.d(TAG, "onDisconnect " + getCallUUID());
WritableNativeMap data = new WritableNativeMap();
data.putString("callUUID", getCallUUID());
ReactInstanceManagerHolder.emitEvent(
@@ -374,7 +367,7 @@ public class ConnectionService extends android.telecom.ConnectionService {
*/
@Override
public void onAbort() {
JitsiMeetLogger.i(TAG + " onAbort " + getCallUUID());
Log.d(TAG, "onAbort " + getCallUUID());
WritableNativeMap data = new WritableNativeMap();
data.putString("callUUID", getCallUUID());
ReactInstanceManagerHolder.emitEvent(
@@ -392,7 +385,9 @@ public class ConnectionService extends android.telecom.ConnectionService {
// What ?! Android will still call this method even if we do not add
// the HOLD capability, so do the same thing as on abort.
// TODO implement HOLD
JitsiMeetLogger.w(TAG + " onHold %s - HOLD is not supported, aborting the call...", getCallUUID());
Log.d(TAG, String.format(
"onHold %s - HOLD is not supported, aborting the call...",
getCallUUID()));
this.onAbort();
}
@@ -405,10 +400,12 @@ public class ConnectionService extends android.telecom.ConnectionService {
*/
@Override
public void onCallAudioStateChanged(CallAudioState state) {
JitsiMeetLogger.d(TAG + " onCallAudioStateChanged: " + state);
RNConnectionService module = ReactInstanceManagerHolder.getNativeModule(RNConnectionService.class);
if (module != null) {
module.onCallAudioStateChange(state);
Log.d(TAG, "onCallAudioStateChanged: " + state);
AudioModeModule audioModeModule
= ReactInstanceManagerHolder
.getNativeModule(AudioModeModule.class);
if (audioModeModule != null) {
audioModeModule.onCallAudioStateChange(state);
}
}
@@ -419,8 +416,10 @@ public class ConnectionService extends android.telecom.ConnectionService {
*/
@Override
public void onStateChanged(int state) {
JitsiMeetLogger.d(
"%s onStateChanged: %s %s", TAG, Connection.stateToString(state), getCallUUID());
Log.d(TAG,
String.format("onStateChanged: %s %s",
Connection.stateToString(state),
getCallUUID()));
if (state == STATE_DISCONNECTED) {
removeConnection(this);

View File

@@ -16,14 +16,14 @@
package org.jitsi.meet.sdk;
import android.util.Log;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.module.annotations.ReactModule;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
/**
* Module implementing an API for sending events from JavaScript to native code.
*/
@@ -67,20 +67,16 @@ class ExternalAPIModule
*/
@ReactMethod
public void sendEvent(String name, ReadableMap data, String scope) {
// Keep track of the current ongoing conference.
OngoingConferenceTracker.getInstance().onExternalAPIEvent(name, data);
// The JavaScript App needs to provide uniquely identifying information
// to the native ExternalAPI module so that the latter may match the
// former to the native BaseReactView which hosts it.
BaseReactView view = BaseReactView.findViewByExternalAPIScope(scope);
if (view != null) {
JitsiMeetLogger.d(TAG + " Sending event: " + name + " with data: " + data);
try {
view.onExternalAPIEvent(name, data);
} catch(Exception e) {
JitsiMeetLogger.e(e, TAG + " onExternalAPIEvent: error sending event");
Log.e(TAG, "onExternalAPIEvent: error sending event", e);
}
}
}

View File

@@ -1,57 +0,0 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;
import com.squareup.duktape.Duktape;
@ReactModule(name = JavaScriptSandboxModule.NAME)
class JavaScriptSandboxModule extends ReactContextBaseJavaModule {
public static final String NAME = "JavaScriptSandbox";
public JavaScriptSandboxModule(ReactApplicationContext reactContext) {
super(reactContext);
}
/**
* Evaluates the given code in a Duktape VM.
* @param code - The code that needs to evaluated.
* @param promise - Resolved with the output in case of success or rejected with an exception
* in case of failure.
*/
@ReactMethod
public void evaluate(String code, Promise promise) {
Duktape vm = Duktape.create();
try {
Object res = vm.evaluate(code);
promise.resolve(res.toString());
} catch (Throwable tr) {
promise.reject(tr);
} finally {
vm.close();
}
}
@Override
public String getName() {
return NAME;
}
}

View File

@@ -33,21 +33,9 @@ public class JitsiMeet {
}
public static void setDefaultConferenceOptions(JitsiMeetConferenceOptions options) {
if (options != null && options.getRoom() != null) {
throw new RuntimeException("'room' must be null in the default conference options");
}
defaultConferenceOptions = options;
}
/**
* Returns the current conference URL as a string.
*
* @return the current conference URL.
*/
public static String getCurrentConference() {
return OngoingConferenceTracker.getInstance().getCurrentConference();
}
/**
* Helper to get the default conference options as a {@link Bundle}.
*

View File

@@ -20,13 +20,12 @@ import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import com.facebook.react.modules.core.PermissionListener;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.Map;
@@ -39,8 +38,8 @@ public class JitsiMeetActivity extends FragmentActivity
protected static final String TAG = JitsiMeetActivity.class.getSimpleName();
private static final String ACTION_JITSI_MEET_CONFERENCE = "org.jitsi.meet.CONFERENCE";
private static final String JITSI_MEET_CONFERENCE_OPTIONS = "JitsiMeetConferenceOptions";
public static final String ACTION_JITSI_MEET_CONFERENCE = "org.jitsi.meet.CONFERENCE";
public static final String JITSI_MEET_CONFERENCE_OPTIONS = "JitsiMeetConferenceOptions";
// Helpers for starting the activity
//
@@ -72,24 +71,6 @@ public class JitsiMeetActivity extends FragmentActivity
}
}
@Override
public void onDestroy() {
// Here we are trying to handle the following corner case: an application using the SDK
// is using this Activity for displaying meetings, but there is another "main" Activity
// with other content. If this Activity is "swiped out" from the recent list we will get
// Activity#onDestroy() called without warning. At this point we can try to leave the
// current meeting, but when our view is detached from React the JS <-> Native bridge won't
// be operational so the external API won't be able to notify the native side that the
// conference terminated. Thus, try our best to clean up.
leave();
if (AudioModeModule.useConnectionService()) {
ConnectionService.abortConnections();
}
JitsiMeetOngoingConferenceService.abort(this);
super.onDestroy();
}
@Override
public void finish() {
leave();
@@ -162,11 +143,6 @@ public class JitsiMeetActivity extends FragmentActivity
// Activity lifecycle methods
//
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
JitsiMeetActivityDelegate.onActivityResult(this, requestCode, resultCode, data);
}
@Override
public void onBackPressed() {
JitsiMeetActivityDelegate.onBackPressed();
@@ -174,8 +150,6 @@ public class JitsiMeetActivity extends FragmentActivity
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
JitsiMeetConferenceOptions options;
if ((options = getConferenceOptions(intent)) != null) {
@@ -209,19 +183,17 @@ public class JitsiMeetActivity extends FragmentActivity
@Override
public void onConferenceJoined(Map<String, Object> data) {
JitsiMeetLogger.i("Conference joined: " + data);
// Launch the service for the ongoing notification.
JitsiMeetOngoingConferenceService.launch(this);
Log.d(TAG, "Conference joined: " + data);
}
@Override
public void onConferenceTerminated(Map<String, Object> data) {
JitsiMeetLogger.i("Conference terminated: " + data);
Log.d(TAG, "Conference terminated: " + data);
finish();
}
@Override
public void onConferenceWillJoin(Map<String, Object> data) {
JitsiMeetLogger.i("Conference will join: " + data);
Log.d(TAG, "Conference will join: " + data);
}
}

View File

@@ -25,7 +25,6 @@ import android.os.Build;
import com.calendarevents.CalendarEventsPackage;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.core.PermissionListener;
/**
@@ -118,13 +117,7 @@ public class JitsiMeetActivityDelegate {
= ReactInstanceManagerHolder.getReactInstanceManager();
if (reactInstanceManager != null) {
// Try to avoid a crash because some devices trip on this assert:
// https://github.com/facebook/react-native/blob/df4e67fe75d781d1eb264128cadf079989542755/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java#L512
// Why this happens is a mystery wrapped in an enigma.
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
if (reactContext != null && activity == reactContext.getCurrentActivity()) {
reactInstanceManager.onHostPause(activity);
}
reactInstanceManager.onHostPause(activity);
}
}

View File

@@ -1,6 +1,6 @@
package org.jitsi.meet.sdk;
import androidx.core.app.ActivityCompat;
import android.support.v4.app.ActivityCompat;
import com.facebook.react.modules.core.PermissionAwareActivity;

View File

@@ -54,11 +54,6 @@ public class JitsiMeetConferenceOptions implements Parcelable {
*/
private Bundle colorScheme;
/**
* Feature flags. See: https://github.com/jitsi/jitsi-meet/blob/master/react/features/base/flags/constants.js
*/
private Bundle featureFlags;
/**
* Set to {@code true} to join the conference with audio / video muted or to start in audio
* only mode respectively.
@@ -68,49 +63,10 @@ public class JitsiMeetConferenceOptions implements Parcelable {
private Boolean videoMuted;
/**
* USer information, to be used when no token is specified.
* Set to {@code true} to enable the welcome page. Typically SDK users won't need this enabled
* since the host application decides which meeting to join.
*/
private JitsiMeetUserInfo userInfo;
public URL getServerURL() {
return serverURL;
}
public String getRoom() {
return room;
}
public String getSubject() {
return subject;
}
public String getToken() {
return token;
}
public Bundle getColorScheme() {
return colorScheme;
}
public Bundle getFeatureFlags() {
return featureFlags;
}
public boolean getAudioMuted() {
return audioMuted;
}
public boolean getAudioOnly() {
return audioOnly;
}
public boolean getVideoMuted() {
return videoMuted;
}
public JitsiMeetUserInfo getUserInfo() {
return userInfo;
}
private Boolean welcomePageEnabled;
/**
* Class used to build the immutable {@link JitsiMeetConferenceOptions} object.
@@ -122,16 +78,14 @@ public class JitsiMeetConferenceOptions implements Parcelable {
private String token;
private Bundle colorScheme;
private Bundle featureFlags;
private Boolean audioMuted;
private Boolean audioOnly;
private Boolean videoMuted;
private JitsiMeetUserInfo userInfo;
private Boolean welcomePageEnabled;
public Builder() {
featureFlags = new Bundle();
}
/**\
@@ -232,31 +186,7 @@ public class JitsiMeetConferenceOptions implements Parcelable {
* @return - The {@link Builder} object itself so the method calls can be chained.
*/
public Builder setWelcomePageEnabled(boolean enabled) {
this.featureFlags.putBoolean("welcomepage.enabled", enabled);
return this;
}
public Builder setFeatureFlag(String flag, boolean value) {
this.featureFlags.putBoolean(flag, value);
return this;
}
public Builder setFeatureFlag(String flag, String value) {
this.featureFlags.putString(flag, value);
return this;
}
public Builder setFeatureFlag(String flag, int value) {
this.featureFlags.putInt(flag, value);
return this;
}
public Builder setUserInfo(JitsiMeetUserInfo userInfo) {
this.userInfo = userInfo;
this.welcomePageEnabled = enabled;
return this;
}
@@ -274,11 +204,10 @@ public class JitsiMeetConferenceOptions implements Parcelable {
options.subject = this.subject;
options.token = this.token;
options.colorScheme = this.colorScheme;
options.featureFlags = this.featureFlags;
options.audioMuted = this.audioMuted;
options.audioOnly = this.audioOnly;
options.videoMuted = this.videoMuted;
options.userInfo = this.userInfo;
options.welcomePageEnabled = this.welcomePageEnabled;
return options;
}
@@ -292,30 +221,30 @@ public class JitsiMeetConferenceOptions implements Parcelable {
subject = in.readString();
token = in.readString();
colorScheme = in.readBundle();
featureFlags = in.readBundle();
userInfo = new JitsiMeetUserInfo(in.readBundle());
byte tmpAudioMuted = in.readByte();
audioMuted = tmpAudioMuted == 0 ? null : tmpAudioMuted == 1;
byte tmpAudioOnly = in.readByte();
audioOnly = tmpAudioOnly == 0 ? null : tmpAudioOnly == 1;
byte tmpVideoMuted = in.readByte();
videoMuted = tmpVideoMuted == 0 ? null : tmpVideoMuted == 1;
byte tmpWelcomePageEnabled = in.readByte();
welcomePageEnabled = tmpWelcomePageEnabled == 0 ? null : tmpWelcomePageEnabled == 1;
}
Bundle asProps() {
Bundle props = new Bundle();
// Android always has the PiP flag set by default.
if (!featureFlags.containsKey("pip.enabled")) {
featureFlags.putBoolean("pip.enabled", true);
}
props.putBundle("flags", featureFlags);
if (colorScheme != null) {
props.putBundle("colorScheme", colorScheme);
}
if (welcomePageEnabled != null) {
props.putBoolean("welcomePageEnabled", welcomePageEnabled);
}
// TODO: get rid of this.
props.putBoolean("pictureInPictureEnabled", true);
Bundle config = new Bundle();
if (audioMuted != null) {
@@ -349,10 +278,6 @@ public class JitsiMeetConferenceOptions implements Parcelable {
urlProps.putString("jwt", token);
}
if (userInfo != null) {
props.putBundle("userInfo", userInfo.asBundle());
}
urlProps.putBundle("config", config);
props.putBundle("url", urlProps);
@@ -380,11 +305,10 @@ public class JitsiMeetConferenceOptions implements Parcelable {
dest.writeString(subject);
dest.writeString(token);
dest.writeBundle(colorScheme);
dest.writeBundle(featureFlags);
dest.writeBundle(userInfo != null ? userInfo.asBundle() : new Bundle());
dest.writeByte((byte) (audioMuted == null ? 0 : audioMuted ? 1 : 2));
dest.writeByte((byte) (audioOnly == null ? 0 : audioOnly ? 1 : 2));
dest.writeByte((byte) (videoMuted == null ? 0 : videoMuted ? 1 : 2));
dest.writeByte((byte) (welcomePageEnabled == null ? 0 : welcomePageEnabled ? 1 : 2));
}
@Override

View File

@@ -19,14 +19,15 @@ package org.jitsi.meet.sdk;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.net.URL;
/**
* Base {@link Fragment} for applications integrating Jitsi Meet at a higher level. It
* contains all the required wiring between the {@code JitsiMeetView} and

View File

@@ -1,121 +0,0 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.app.Notification;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
/**
* This class implements an Android {@link Service}, a foreground one specifically, and it's
* responsible for presenting an ongoing notification when a conference is in progress.
* The service will help keep the app running while in the background.
*
* See: https://developer.android.com/guide/components/services
*/
public class JitsiMeetOngoingConferenceService extends Service
implements OngoingConferenceTracker.OngoingConferenceListener {
private static final String TAG = JitsiMeetOngoingConferenceService.class.getSimpleName();
static final class Actions {
static final String START = TAG + ":START";
static final String HANGUP = TAG + ":HANGUP";
}
static void launch(Context context) {
OngoingNotification.createOngoingConferenceNotificationChannel();
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
intent.setAction(Actions.START);
ComponentName componentName;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
componentName = context.startForegroundService(intent);
} else {
componentName = context.startService(intent);
}
if (componentName == null) {
JitsiMeetLogger.w(TAG + " Ongoing conference service not started");
}
}
static void abort(Context context) {
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
context.stopService(intent);
}
@Override
public void onCreate() {
super.onCreate();
OngoingConferenceTracker.getInstance().addListener(this);
}
@Override
public void onDestroy() {
OngoingConferenceTracker.getInstance().removeListener(this);
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
final String action = intent.getAction();
if (Actions.START.equals(action)) {
Notification notification = OngoingNotification.buildOngoingConferenceNotification();
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
JitsiMeetLogger.i(TAG + " Service started");
}
} else if (Actions.HANGUP.equals(action)) {
JitsiMeetLogger.i(TAG + " Hangup requested");
// Abort all ongoing calls
if (AudioModeModule.useConnectionService()) {
ConnectionService.abortConnections();
}
stopSelf();
} else {
JitsiMeetLogger.w(TAG + " Unknown action received: " + action);
stopSelf();
}
return START_NOT_STICKY;
}
@Override
public void onCurrentConferenceChanged(String conferenceUrl) {
if (conferenceUrl == null) {
stopSelf();
JitsiMeetLogger.i(TAG + "Service stopped");
}
}
}

View File

@@ -1,51 +0,0 @@
/*
* Copyright @ 2018-present 8x8, Inc.
* Copyright @ 2017-2018 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
class JitsiMeetUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
private final Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler;
public static void register() {
Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
JitsiMeetUncaughtExceptionHandler uncaughtExceptionHandler
= new JitsiMeetUncaughtExceptionHandler(defaultUncaughtExceptionHandler);
Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler);
}
private JitsiMeetUncaughtExceptionHandler(Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler) {
this.defaultUncaughtExceptionHandler = defaultUncaughtExceptionHandler;
}
@Override
public void uncaughtException(Thread t, Throwable e) {
JitsiMeetLogger.e(e, this.getClass().getSimpleName() + " FATAL ERROR");
// Abort all ConnectionService ongoing calls
if (AudioModeModule.useConnectionService()) {
ConnectionService.abortConnections();
}
if (defaultUncaughtExceptionHandler != null) {
defaultUncaughtExceptionHandler.uncaughtException(t, e);
}
}
}

View File

@@ -1,107 +0,0 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.os.Bundle;
import java.net.MalformedURLException;
import java.net.URL;
/**
* This class represents user information to be passed to {@link JitsiMeetConferenceOptions} for
* identifying a user.
*/
public class JitsiMeetUserInfo {
/**
* User's display name.
*/
private String displayName;
/**
* User's email address.
*/
private String email;
/**
* User's avatar URL.
*/
private URL avatar;
public JitsiMeetUserInfo() {}
public JitsiMeetUserInfo(Bundle b) {
super();
if (b.containsKey("displayName")) {
displayName = b.getString("displayName");
}
if (b.containsKey("email")) {
email = b.getString("email");
}
if (b.containsKey("avatarURL")) {
String avatarURL = b.getString("avatarURL");
try {
avatar = new URL(avatarURL);
} catch (MalformedURLException e) {
}
}
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public URL getAvatar() {
return avatar;
}
public void setAvatar(URL avatar) {
this.avatar = avatar;
}
Bundle asBundle() {
Bundle b = new Bundle();
if (displayName != null) {
b.putString("displayName", displayName);
}
if (email != null) {
b.putString("email", email);
}
if (avatar != null) {
b.putString("avatarURL", avatar.toString());
}
return b;
}
}

View File

@@ -19,19 +19,17 @@ package org.jitsi.meet.sdk;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import com.facebook.react.bridge.ReadableMap;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.lang.reflect.Method;
import java.util.Map;
public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener>
implements OngoingConferenceTracker.OngoingConferenceListener {
public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener> {
/**
* The {@code Method}s of {@code JitsiMeetViewListener} by event name i.e.
@@ -40,6 +38,12 @@ public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener>
private static final Map<String, Method> LISTENER_METHODS
= ListenerUtils.mapListenerMethods(JitsiMeetViewListener.class);
/**
* The {@link Log} tag which identifies the source of the log messages of
* {@code JitsiMeetView}.
*/
private static final String TAG = JitsiMeetView.class.getSimpleName();
/**
* The URL of the current conference.
*/
@@ -102,14 +106,6 @@ public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener>
if (!(context instanceof JitsiMeetActivityInterface)) {
throw new RuntimeException("Enclosing Activity must implement JitsiMeetActivityInterface");
}
OngoingConferenceTracker.getInstance().addListener(this);
}
@Override
public void dispose() {
OngoingConferenceTracker.getInstance().removeListener(this);
super.dispose();
}
/**
@@ -132,7 +128,7 @@ public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener>
try {
pipModule.enterPictureInPicture();
} catch (RuntimeException re) {
JitsiMeetLogger.e(re, "Failed to enter PiP mode");
Log.e(TAG, "failed to enter PiP mode", re);
}
}
}
@@ -177,17 +173,27 @@ public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener>
}
/**
* Handler for {@link OngoingConferenceTracker} events.
* @param conferenceUrl
* The internal processing for the URL of the current conference set on the
* associated {@link JitsiMeetView}.
*
* @param eventName the name of the external API event to be processed
* @param eventData the details/specifics of the event to process determined
* by/associated with the specified {@code eventName}.
*/
@Override
public void onCurrentConferenceChanged(String conferenceUrl) {
// This property was introduced in order to address
// an exception in the Picture-in-Picture functionality which arose
// because of delays related to bridging between JavaScript and Java. To
// reduce these delays do not wait for the call to be transferred to the
// UI thread.
this.url = conferenceUrl;
private void maybeSetViewURL(String eventName, ReadableMap eventData) {
String url = eventData.getString("url");
switch(eventName) {
case "CONFERENCE_WILL_JOIN":
this.url = url;
break;
case "CONFERENCE_TERMINATED":
if (url != null && url.equals(this.url)) {
this.url = null;
}
break;
}
}
/**
@@ -199,6 +205,13 @@ public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener>
*/
@Override
protected void onExternalAPIEvent(String name, ReadableMap data) {
// XXX The JitsiMeetView property URL was introduced in order to address
// an exception in the Picture-in-Picture functionality which arose
// because of delays related to bridging between JavaScript and Java. To
// reduce these delays do not wait for the call to be transferred to the
// UI thread.
maybeSetViewURL(name, data);
onExternalAPIEvent(LISTENER_METHODS, name, data);
}
}

View File

@@ -1,73 +0,0 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import javax.annotation.Nonnull;
/**
* Module implementing a "bridge" between the JS loggers and the native one.
*/
@ReactModule(name = LogBridgeModule.NAME)
class LogBridgeModule extends ReactContextBaseJavaModule {
public static final String NAME = "LogBridge";
public LogBridgeModule(@Nonnull ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return NAME;
}
@ReactMethod
public void trace(final String message) {
JitsiMeetLogger.v(message);
}
@ReactMethod
public void debug(final String message) {
JitsiMeetLogger.d(message);
}
@ReactMethod
public void info(final String message) {
JitsiMeetLogger.i(message);
}
@ReactMethod
public void log(final String message) {
JitsiMeetLogger.i(message);
}
@ReactMethod
public void warn(final String message) {
JitsiMeetLogger.w(message);
}
@ReactMethod
public void error(final String message) {
JitsiMeetLogger.e(message);
}
}

View File

@@ -1,99 +0,0 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import com.facebook.react.bridge.ReadableMap;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
/**
* Helper class to keep track of what the current conference is.
*/
class OngoingConferenceTracker {
private static final OngoingConferenceTracker instance = new OngoingConferenceTracker();
private static final String CONFERENCE_WILL_JOIN = "CONFERENCE_WILL_JOIN";
private static final String CONFERENCE_TERMINATED = "CONFERENCE_TERMINATED";
private final Collection<OngoingConferenceListener> listeners =
Collections.synchronizedSet(new HashSet<OngoingConferenceListener>());
private String currentConference;
public OngoingConferenceTracker() {
}
public static OngoingConferenceTracker getInstance() {
return instance;
}
/**
* Gets the current active conference URL.
*
* @return - The current conference URL as a String.
*/
synchronized String getCurrentConference() {
return currentConference;
}
synchronized void onExternalAPIEvent(String name, ReadableMap data) {
if (!data.hasKey("url")) {
return;
}
String url = data.getString("url");
if (url == null) {
return;
}
switch(name) {
case CONFERENCE_WILL_JOIN:
currentConference = url;
updateListeners();
break;
case CONFERENCE_TERMINATED:
if (url.equals(currentConference)) {
currentConference = null;
updateListeners();
}
break;
}
}
void addListener(OngoingConferenceListener listener) {
listeners.add(listener);
}
void removeListener(OngoingConferenceListener listener) {
listeners.remove(listener);
}
private void updateListeners() {
synchronized (listeners) {
for (OngoingConferenceListener listener : listeners) {
listener.onCurrentConferenceChanged(currentConference);
}
}
}
public interface OngoingConferenceListener {
void onCurrentConferenceChanged(String conferenceUrl);
}
}

View File

@@ -1,119 +0,0 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import androidx.core.app.NotificationCompat;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.Random;
/**
* Helper class for creating the ongoing notification which is used with
* {@link JitsiMeetOngoingConferenceService}. It allows the user to easily get back to the app
* and to hangup from within the notification itself.
*/
class OngoingNotification {
private static final String TAG = OngoingNotification.class.getSimpleName();
private static final String CHANNEL_ID = "JitsiNotificationChannel";
private static final String CHANNEL_NAME = "Ongoing Conference Notifications";
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
static void createOngoingConferenceNotificationChannel() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}
Context context = ReactInstanceManagerHolder.getCurrentActivity();
if (context == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification channel: no current context");
return;
}
NotificationManager notificationManager
= (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel
= notificationManager.getNotificationChannel(CHANNEL_ID);
if (channel != null) {
// The channel was already created, no need to do it again.
return;
}
channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
channel.enableLights(false);
channel.enableVibration(false);
channel.setShowBadge(false);
notificationManager.createNotificationChannel(channel);
}
static Notification buildOngoingConferenceNotification() {
Context context = ReactInstanceManagerHolder.getCurrentActivity();
if (context == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification: no current context");
return null;
}
Intent notificationIntent = new Intent(context, context.getClass());
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
NotificationCompat.Builder builder;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder = new NotificationCompat.Builder(context, CHANNEL_ID);
} else {
builder = new NotificationCompat.Builder(context);
}
builder
.setCategory(NotificationCompat.CATEGORY_CALL)
.setContentTitle(context.getString(R.string.ongoing_notification_title))
.setContentText(context.getString(R.string.ongoing_notification_text))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.setOngoing(true)
.setAutoCancel(false)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setUsesChronometer(true)
.setOnlyAlertOnce(true)
.setSmallIcon(context.getResources().getIdentifier("ic_notification", "drawable", context.getPackageName()));
// Add a "hang-up" action only if we are using ConnectionService.
if (AudioModeModule.useConnectionService()) {
Intent hangupIntent = new Intent(context, JitsiMeetOngoingConferenceService.class);
hangupIntent.setAction(JitsiMeetOngoingConferenceService.Actions.HANGUP);
PendingIntent hangupPendingIntent
= PendingIntent.getService(context, 0, hangupIntent, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Action hangupAction = new NotificationCompat.Action(0, "Hang up", hangupPendingIntent);
builder.addAction(hangupAction);
}
return builder.build();
}
}

View File

@@ -20,6 +20,7 @@ import android.annotation.TargetApi;
import android.app.Activity;
import android.app.PictureInPictureParams;
import android.os.Build;
import android.util.Log;
import android.util.Rational;
import com.facebook.react.bridge.Promise;
@@ -28,8 +29,6 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
@ReactModule(name = PictureInPictureModule.NAME)
class PictureInPictureModule
extends ReactContextBaseJavaModule {
@@ -71,7 +70,7 @@ class PictureInPictureModule
throw new IllegalStateException("No current Activity!");
}
JitsiMeetLogger.i(TAG + " Entering Picture-in-Picture");
Log.d(TAG, "Entering Picture-in-Picture");
PictureInPictureParams.Builder builder
= new PictureInPictureParams.Builder()

View File

@@ -5,12 +5,13 @@ import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.RequiresApi;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import androidx.annotation.RequiresApi;
import android.util.Log;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
@@ -19,8 +20,6 @@ import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.module.annotations.ReactModule;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
/**
* The react-native side of Jitsi Meet's {@link ConnectionService}. Exposes
* the Java Script API.
@@ -29,18 +28,13 @@ import org.jitsi.meet.sdk.log.JitsiMeetLogger;
*/
@RequiresApi(api = Build.VERSION_CODES.O)
@ReactModule(name = RNConnectionService.NAME)
class RNConnectionService extends ReactContextBaseJavaModule {
class RNConnectionService
extends ReactContextBaseJavaModule {
public static final String NAME = "ConnectionService";
private static final String TAG = ConnectionService.TAG;
/**
* Handler for dealing with call state changes. We are acting as a proxy between ConnectionService
* and other modules such as {@link AudioModeModule}.
*/
private CallAudioStateListener callAudioStateListener;
/**
* Sets the audio route on all existing {@link android.telecom.Connection}s
*
@@ -80,26 +74,18 @@ class RNConnectionService extends ReactContextBaseJavaModule {
String handle,
boolean hasVideo,
Promise promise) {
JitsiMeetLogger.d("%s startCall UUID=%s, h=%s, v=%s",
TAG,
Log.d(TAG,
String.format("startCall UUID=%s, h=%s, v=%s",
callUUID,
handle,
hasVideo);
hasVideo));
ReactApplicationContext ctx = getReactApplicationContext();
Uri address = Uri.fromParts(PhoneAccount.SCHEME_SIP, handle, null);
PhoneAccountHandle accountHandle;
try {
accountHandle
= ConnectionService.registerPhoneAccount(getReactApplicationContext(), address, callUUID);
} catch (Throwable tr) {
JitsiMeetLogger.e(tr, TAG + " error in startCall");
promise.reject(tr);
return;
}
PhoneAccountHandle accountHandle
= ConnectionService.registerPhoneAccount(
getReactApplicationContext(), address, callUUID);
Bundle extras = new Bundle();
extras.putParcelable(
@@ -113,23 +99,15 @@ class RNConnectionService extends ReactContextBaseJavaModule {
ConnectionService.registerStartCallPromise(callUUID, promise);
TelecomManager tm = null;
try {
tm = (TelecomManager) ctx.getSystemService(Context.TELECOM_SERVICE);
TelecomManager tm
= (TelecomManager) ctx.getSystemService(
Context.TELECOM_SERVICE);
tm.placeCall(address, extras);
} catch (Throwable tr) {
JitsiMeetLogger.e(tr, TAG + " error in startCall");
if (tm != null) {
try {
tm.unregisterPhoneAccount(accountHandle);
} catch (Throwable tr1) {
// UnsupportedOperationException: System does not support feature android.software.connectionservice
// was observed here. Ignore.
}
}
} catch (Exception e) {
ConnectionService.unregisterStartCallPromise(callUUID);
promise.reject(tr);
promise.reject(e);
}
}
@@ -140,7 +118,7 @@ class RNConnectionService extends ReactContextBaseJavaModule {
*/
@ReactMethod
public void reportCallFailed(String callUUID) {
JitsiMeetLogger.d(TAG + " reportCallFailed " + callUUID);
Log.d(TAG, "reportCallFailed " + callUUID);
ConnectionService.setConnectionDisconnected(
callUUID,
new DisconnectCause(DisconnectCause.ERROR));
@@ -153,7 +131,7 @@ class RNConnectionService extends ReactContextBaseJavaModule {
*/
@ReactMethod
public void endCall(String callUUID) {
JitsiMeetLogger.d(TAG + " endCall " + callUUID);
Log.d(TAG, "endCall " + callUUID);
ConnectionService.setConnectionDisconnected(
callUUID,
new DisconnectCause(DisconnectCause.LOCAL));
@@ -165,13 +143,9 @@ class RNConnectionService extends ReactContextBaseJavaModule {
* @param callUUID - the call's UUID.
*/
@ReactMethod
public void reportConnectedOutgoingCall(String callUUID, Promise promise) {
JitsiMeetLogger.d(TAG + " reportConnectedOutgoingCall " + callUUID);
if (ConnectionService.setConnectionActive(callUUID)) {
promise.resolve(null);
} else {
promise.reject("CONNECTION_NOT_FOUND_ERROR", "Connection wasn't found.");
}
public void reportConnectedOutgoingCall(String callUUID) {
Log.d(TAG, "reportConnectedOutgoingCall " + callUUID);
ConnectionService.setConnectionActive(callUUID);
}
@Override
@@ -192,28 +166,4 @@ class RNConnectionService extends ReactContextBaseJavaModule {
public void updateCall(String callUUID, ReadableMap callState) {
ConnectionService.updateCall(callUUID, callState);
}
public CallAudioStateListener getCallAudioStateListener() {
return callAudioStateListener;
}
public void setCallAudioStateListener(CallAudioStateListener callAudioStateListener) {
this.callAudioStateListener = callAudioStateListener;
}
/**
* Handler for call state changes. {@code ConnectionServiceImpl} will call this handler when the
* call audio state changes.
*
* @param callAudioState The current call's audio state.
*/
void onCallAudioStateChange(android.telecom.CallAudioState callAudioState) {
if (callAudioStateListener != null) {
callAudioStateListener.onCallAudioStateChange(callAudioState);
}
}
interface CallAudioStateListener {
void onCallAudioStateChange(android.telecom.CallAudioState callAudioState);
}
}

View File

@@ -1,5 +1,6 @@
/*
* Copyright @ 2017-present 8x8, Inc.
* Copyright @ 2019-present 8x8, Inc.
* Copyright @ 2017-2018 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,11 +17,9 @@
package org.jitsi.meet.sdk;
import android.app.Activity;
import android.app.Application;
import android.support.annotation.Nullable;
import androidx.annotation.Nullable;
import com.facebook.hermes.reactexecutor.HermesExecutorFactory;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
@@ -29,16 +28,6 @@ import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.devsupport.DevInternalSettings;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.soloader.SoLoader;
import com.oney.WebRTCModule.RTCVideoViewManager;
import com.oney.WebRTCModule.WebRTCModule;
import org.webrtc.SoftwareVideoDecoderFactory;
import org.webrtc.SoftwareVideoEncoderFactory;
import org.webrtc.audio.AudioDeviceModule;
import org.webrtc.audio.JavaAudioDeviceModule;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
@@ -57,7 +46,8 @@ class ReactInstanceManagerHolder {
*/
private static ReactInstanceManager reactInstanceManager;
private static List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
private static List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> nativeModules
= new ArrayList<>(Arrays.<NativeModule>asList(
new AndroidSettingsModule(reactContext),
@@ -65,9 +55,7 @@ class ReactInstanceManagerHolder {
new AudioModeModule(reactContext),
new DropboxModule(reactContext),
new ExternalAPIModule(reactContext),
new JavaScriptSandboxModule(reactContext),
new LocaleDetector(reactContext),
new LogBridgeModule(reactContext),
new PictureInPictureModule(reactContext),
new ProximityModule(reactContext),
new WiFiStatsModule(reactContext),
@@ -77,19 +65,6 @@ class ReactInstanceManagerHolder {
nativeModules.add(new RNConnectionService(reactContext));
}
// Initialize the WebRTC module by hand, since we want to override some
// initialization options.
WebRTCModule.Options options = new WebRTCModule.Options();
AudioDeviceModule adm = JavaAudioDeviceModule.builder(reactContext)
.createAudioDeviceModule();
options.setAudioDeviceModule(adm);
options.setVideoDecoderFactory(new SoftwareVideoDecoderFactory());
options.setVideoEncoderFactory(new SoftwareVideoEncoderFactory());
nativeModules.add(new WebRTCModule(reactContext, options));
try {
Class<?> amplitudeModuleClass = Class.forName("org.jitsi.meet.sdk.AmplitudeModule");
Constructor constructor = amplitudeModuleClass.getConstructor(ReactApplicationContext.class);
@@ -101,13 +76,6 @@ class ReactInstanceManagerHolder {
return nativeModules;
}
private static List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
// WebRTC, see createNativeModules for details.
new RTCVideoViewManager()
);
}
/**
* Helper function to send an event to JavaScript.
*
@@ -152,18 +120,6 @@ class ReactInstanceManagerHolder {
? reactContext.getNativeModule(nativeModuleClass) : null;
}
/**
* Gets the current {@link Activity} linked to React Native.
*
* @return An activity attached to React Native.
*/
static Activity getCurrentActivity() {
ReactContext reactContext
= reactInstanceManager != null
? reactInstanceManager.getCurrentReactContext() : null;
return reactContext != null ? reactContext.getCurrentActivity() : null;
}
static ReactInstanceManager getReactInstanceManager() {
return reactInstanceManager;
}
@@ -174,25 +130,24 @@ class ReactInstanceManagerHolder {
* time. All {@code ReactRootView} instances will be tied to the one and
* only {@code ReactInstanceManager}.
*
* @param activity {@code Activity} current running Activity.
* @param application {@code Application} instance which is running.
*/
static void initReactInstanceManager(Activity activity) {
static void initReactInstanceManager(Application application) {
if (reactInstanceManager != null) {
return;
}
SoLoader.init(activity, /* native exopackage */ false);
List<ReactPackage> packages
= new ArrayList<>(Arrays.asList(
new com.BV.LinearGradient.LinearGradientPackage(),
new com.calendarevents.CalendarEventsPackage(),
new com.corbt.keepawake.KCKeepAwakePackage(),
new com.dylanvann.fastimage.FastImageViewPackage(),
new com.facebook.react.shell.MainReactPackage(),
new com.horcrux.svg.SvgPackage(),
new com.oblador.vectoricons.VectorIconsPackage(),
new com.ocetnik.timer.BackgroundTimerPackage(),
new com.oney.WebRTCModule.WebRTCModulePackage(),
new com.reactnativecommunity.asyncstorage.AsyncStoragePackage(),
new com.reactnativecommunity.netinfo.NetInfoPackage(),
new com.reactnativecommunity.webview.RNCWebViewPackage(),
new com.rnimmersive.RNImmersivePackage(),
new com.zmxv.RNSound.RNSoundPackage(),
@@ -201,10 +156,6 @@ class ReactInstanceManagerHolder {
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return ReactInstanceManagerHolder.createNativeModules(reactContext);
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return ReactInstanceManagerHolder.createViewManagers(reactContext);
}
}));
try {
@@ -215,16 +166,11 @@ class ReactInstanceManagerHolder {
// Ignore any error, the module is not compiled when LIBRE_BUILD is enabled.
}
// Use the Hermes JavaScript engine.
HermesExecutorFactory jsFactory = new HermesExecutorFactory();
reactInstanceManager
= ReactInstanceManager.builder()
.setApplication(activity.getApplication())
.setCurrentActivity(activity)
.setApplication(application)
.setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index.android")
.setJavaScriptExecutorFactory(jsFactory)
.addPackages(packages)
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
@@ -236,8 +182,5 @@ class ReactInstanceManagerHolder {
if (devSettings != null) {
devSettings.setBundleDeltasEnabled(false);
}
// Register our uncaught exception handler.
JitsiMeetUncaughtExceptionHandler.register();
}
}

View File

@@ -19,6 +19,7 @@ package org.jitsi.meet.sdk;
import android.content.Context;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.util.Log;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
@@ -26,7 +27,6 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import org.json.JSONArray;
import org.json.JSONObject;
@@ -144,7 +144,8 @@ class WiFiStatsModule
JSONObject result = new JSONObject();
result.put("rssi", rssi)
.put("signal", signalLevel)
.put("timestamp", System.currentTimeMillis());
.put("timestamp",
String.valueOf(System.currentTimeMillis()));
JSONArray addresses = new JSONArray();
@@ -184,15 +185,17 @@ class WiFiStatsModule
}
} catch (SocketException e) {
JitsiMeetLogger.e(e, TAG + " Unable to NetworkInterface.getNetworkInterfaces()");
Log.wtf(TAG,
"Unable to NetworkInterface.getNetworkInterfaces()"
);
}
result.put("addresses", addresses);
promise.resolve(result.toString());
JitsiMeetLogger.d(TAG + " WiFi stats: " + result.toString());
Log.d(TAG, "WiFi stats: " + result.toString());
} catch (Throwable e) {
JitsiMeetLogger.e(e, TAG + " Failed to obtain wifi stats");
Log.e(TAG, "Failed to obtain wifi stats", e);
promise.reject(
new Exception("Failed to obtain wifi stats"));
}

View File

@@ -16,7 +16,7 @@
package org.jitsi.meet.sdk.incoming_call;
import androidx.annotation.NonNull;
import android.support.annotation.NonNull;
public class IncomingCallInfo {
/**

View File

@@ -18,7 +18,7 @@ package org.jitsi.meet.sdk.incoming_call;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import android.support.annotation.NonNull;
import com.facebook.react.bridge.ReadableMap;

Some files were not shown because too many files have changed in this diff Show More