Compare commits
8 Commits
android-sd
...
android-19
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fd2f4052f | ||
|
|
bf6c41adb1 | ||
|
|
59f3dce01c | ||
|
|
6d1b891c4c | ||
|
|
0062f8ae8e | ||
|
|
eb63e19d63 | ||
|
|
cd48f5cb20 | ||
|
|
42e495e359 |
80
.flowconfig
@@ -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.
|
||||
@@ -41,6 +33,7 @@ node_modules/warning/.*
|
||||
[libs]
|
||||
node_modules/react-native/Libraries/react-native/react-native-interface.js
|
||||
node_modules/react-native/flow/
|
||||
node_modules/react-native/flow-github/
|
||||
|
||||
[options]
|
||||
emoji=true
|
||||
@@ -48,18 +41,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 +53,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 +65,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.78.0
|
||||
|
||||
10
.github/ISSUE_TEMPLATE/2-help.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Need help with Jitsi Meet?
|
||||
about: Please ask it in our community at https://community.jitsi.org
|
||||
|
||||
---
|
||||
|
||||
If you have a question about Jitsi Meet that is not a bug report or feature
|
||||
request, please post it in https://community.jitsi.org
|
||||
|
||||
Questions posted to this repository will be closed.
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Need help with Jitsi Meet?
|
||||
url: https://community.jitsi.org
|
||||
about: Please ask it in our community.
|
||||
16
.github/workflows/ci.yml
vendored
@@ -1,16 +0,0 @@
|
||||
name: Simple CI
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
run-ci:
|
||||
name: Build Frontend
|
||||
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
|
||||
- run: make
|
||||
18
.gitignore
vendored
@@ -61,14 +61,17 @@ 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
|
||||
# Bundle artifact
|
||||
*.jsbundle
|
||||
*.framework
|
||||
android/app/debug
|
||||
android/app/release
|
||||
|
||||
# precommit-hook
|
||||
.jshintignore
|
||||
@@ -77,10 +80,3 @@ android/app/release
|
||||
# VSCode files
|
||||
android/.project
|
||||
android/.settings/org.eclipse.buildship.core.prefs
|
||||
|
||||
# Secrets
|
||||
android/app/dropbox.key
|
||||
android/app/google-services.json
|
||||
ios/app/dropbox.key
|
||||
ios/app/GoogleService-Info.plist
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
osx_image: xcode11.1
|
||||
osx_image: xcode10
|
||||
language: objective-c
|
||||
script:
|
||||
- "./ios/travis-ci/build-ipa.sh"
|
||||
after_script:
|
||||
- sleep 10
|
||||
|
||||
@@ -13,7 +13,7 @@ Found a bug and know how to fix it? Great! Please read on.
|
||||
## Contributor License Agreement
|
||||
While the Jitsi projects are released under the
|
||||
[Apache License 2.0](https://github.com/jitsi/jitsi-meet/blob/master/LICENSE), the copyright
|
||||
holder and principal creator is [8x8](https://www.8x8.com/). To
|
||||
holder and principal creator is [Atlassian](https://www.atlassian.com/). To
|
||||
ensure that we can continue making these projects available under an Open Source license,
|
||||
we need you to sign our Apache-based contributor
|
||||
license agreement as either a [corporation](https://jitsi.org/ccla) or an
|
||||
|
||||
18
Makefile
@@ -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,8 @@ 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 \
|
||||
$(BUILD_DIR)/analytics-amplitude.min.js \
|
||||
$(BUILD_DIR)/analytics-amplitude.min.map \
|
||||
$(DEPLOY_DIR)
|
||||
|
||||
deploy-lib-jitsi-meet:
|
||||
@@ -66,11 +63,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,12 +71,12 @@ 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:
|
||||
mkdir -p source_package/jitsi-meet/css && \
|
||||
cp -r *.js *.html resources/*.txt connection_optimization favicon.ico fonts images libs static sounds LICENSE lang source_package/jitsi-meet && \
|
||||
cp -r *.js *.html connection_optimization favicon.ico fonts images libs static sounds LICENSE lang source_package/jitsi-meet && \
|
||||
cp css/all.css source_package/jitsi-meet/css && \
|
||||
(cd source_package ; tar cjf ../jitsi-meet.tar.bz2 jitsi-meet) && \
|
||||
rm -rf source_package
|
||||
|
||||
121
README.md
@@ -1,16 +1,16 @@
|
||||
# Jitsi Meet - Secure, Simple and Scalable Video Conferences
|
||||
|
||||
Jitsi Meet is an open-source (Apache) WebRTC JavaScript application that uses [Jitsi Videobridge](https://jitsi.org/videobridge) to provide high quality, [secure](https://jitsi.org/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).
|
||||
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. You can see Jitsi Meet in action [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 the need for installing anything on your computer. You can also try it out yourself 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.
|
||||
Jitsi Meet allows for very efficient collaboration. It allows users to stream their desktop or only some windows. It also supports shared document editing with Etherpad.
|
||||
|
||||
## Installation
|
||||
|
||||
On the client side, no installation is necessary. You just point your browser to the URL of your deployment. This section is about installing a Jitsi Meet suite on your server and hosting your own conferencing service.
|
||||
On the client side, no installation is necessary. You just point your browser to the URL of your deployment. This section is about installing the Jitsi Meet suite on your server and hosting your own conferencing service.
|
||||
|
||||
Installing Jitsi Meet is a simple experience. For Debian-based system, following the [quick-install](https://github.com/jitsi/jitsi-meet/blob/master/doc/quick-install.md) document, which uses the package system. You can also see a demonstration of the process in [this tutorial video](https://jitsi.org/tutorial).
|
||||
Installing Jitsi Meet is quite a simple experience. For Debian-based systems, we recommend following the [quick-install](https://github.com/jitsi/jitsi-meet/blob/master/doc/quick-install.md) document, which uses the package system. You can also see a demonstration of the process in [this tutorial video](https://jitsi.org/tutorial).
|
||||
|
||||
For other systems, or if you wish to install all components manually, see the [detailed manual installation instructions](https://github.com/jitsi/jitsi-meet/blob/master/doc/manual-install.md).
|
||||
|
||||
@@ -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 >= 8 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' will not do it.
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
So now 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, 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
|
||||
|
||||
@@ -57,18 +115,27 @@ see our [guidelines for contributing](CONTRIBUTING.md).
|
||||
|
||||
## Embedding in external applications
|
||||
|
||||
Jitsi Meet provides a very flexible way of embedding in external applications by using the [Jitsi Meet API](doc/api.md).
|
||||
Jitsi Meet provides a very flexible way of embedding it in external applications by using the [Jitsi Meet API](doc/api.md).
|
||||
|
||||
## Security
|
||||
WebRTC today does not provide a way of conducting multiparty conversations with
|
||||
end-to-end encryption. As a matter of fact, unless you consistently vocally
|
||||
compare DTLS fingerprints with your peers, the same goes for one-to-one calls.
|
||||
As a result when using a Jitsi Meet instance, your stream is encrypted on the
|
||||
network but decrypted on the machine that hosts the bridge.
|
||||
|
||||
The security section here was starting to feel a bit too succinct for the complexity of the topic, so we created a post that covers the topic much more broadly here: https://jitsi.org/security
|
||||
The Jitsi Meet architecture allows you to deploy your own version, including
|
||||
all server components, and in that case your security guarantees will be roughly
|
||||
equivalent to these of a direct one-to-one WebRTC call. This is what's unique to
|
||||
Jitsi Meet in terms of security.
|
||||
|
||||
The section on end-to-end encryption in that document is likely going to be one of the key points of interest: https://jitsi.org/security/#e2ee
|
||||
The [meet.jit.si](https://meet.jit.si) service is maintained by the Jitsi team
|
||||
at [8x8](https://8x8.com).
|
||||
|
||||
## Security issues
|
||||
|
||||
For information on reporting security vulnerabilities in Jitsi Meet, see [SECURITY.md](./SECURITY.md).
|
||||
## 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
|
||||
|
||||
Jitsi Meet started out as a sample conferencing application using Jitsi Videobridge. It was originally developed by ESTOS' developer Philipp Hancke who then contributed it to the community where development continues with joint forces!
|
||||
Jitsi Meet started out as a sample conferencing application using Jitsi Videobridge. It was originally developed by then ESTOS' developer Philipp Hancke who then contributed it to the community where development continues with joint forces!
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# Security
|
||||
|
||||
## Reporting security issuess
|
||||
|
||||
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.**
|
||||
@@ -1,15 +1,8 @@
|
||||
# Jitsi Meet SDK for Android
|
||||
|
||||
## Sample applications using the SDK
|
||||
|
||||
If you want to see how easy integrating the Jitsi Meet SDK into a native application is, take a look at the
|
||||
[sample applications repository](https://github.com/jitsi/jitsi-meet-sdk-samples).
|
||||
|
||||
## Build your own, or use a pre-build SDK artifacts/binaries
|
||||
|
||||
Jitsi conveniently provides a pre-build SDK artifacts/binaries in its Maven repository. When you do not require any
|
||||
modification to the SDK itself or any of its dependencies, it's suggested to use the pre-build SDK. This avoids the
|
||||
complexity of building and installing your own SDK artifacts/binaries.
|
||||
Jitsi conveniently provides a pre-build SDK artifacts/binaries in its Maven repository. When you do not require any modification to the SDK itself, it's suggested to use the pre-build SDK. This avoids the complexity of building and installing your own SDK artifacts/binaries.
|
||||
|
||||
### Use pre-build SDK artifacts/binaries
|
||||
|
||||
@@ -36,10 +29,23 @@ Dependency definitions belong in the individual module `build.gradle` files:
|
||||
```gradle
|
||||
dependencies {
|
||||
// (other dependencies)
|
||||
implementation ('org.jitsi.react:jitsi-meet-sdk:2.+') { transitive = true }
|
||||
implementation ('org.jitsi.react:jitsi-meet-sdk:+') { transitive = true }
|
||||
}
|
||||
```
|
||||
|
||||
Also, enable 32bit mode for react-native, since react-native only supports 32bit apps. (If you have a 64bit device, it will not run unless this setting it set)
|
||||
|
||||
```gradle
|
||||
android {
|
||||
...
|
||||
defaultConfig {
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "x86"
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
### Build and use your own SDK artifacts/binaries
|
||||
|
||||
<details>
|
||||
@@ -51,20 +57,53 @@ A note on dependencies: Apart from the SDK, Jitsi also publishes a binary Maven
|
||||
|
||||
If you want to use a SDK that is built from source, you will likely benefit from composing a local Maven repository that contains these dependencies. The text below describes how you create a repository that includes both the SDK as well as these dependencies. For illustration purposes, we'll define the location of this local Maven repository as `/tmp/repo`
|
||||
|
||||
In source code form, the Android SDK dependencies are locked/pinned by package.json and package-lock.json of the Jitsi Meet project. To obtain the data, execute NPM in the jitsi-meet project directory:
|
||||
In source code form, the Android SDK dependencies are locked/pinned by package.json and package-lock.json of the Jitsi Meet project. To obtain the data, execute NPM in the parent directory:
|
||||
|
||||
npm install
|
||||
$ (cd ..; npm install)
|
||||
|
||||
This will pull in the dependencies in either binary format, or in source code format, somewhere under /node_modules/
|
||||
|
||||
Third-party React Native _modules_, which Jitsi Meet SDK for Android depends on, are download by NPM in source code
|
||||
or binary form. These need to be assembled into Maven artifacts, and then published to your local Maven repository.
|
||||
A script is provided to facilitate this. From the root of the jitsi-meet project repository, run:
|
||||
At the time of writing, there are two packages pulled in in binary format.
|
||||
|
||||
./android/scripts/release-sdk.sh /tmp/repo
|
||||
To copy React Native to your local Maven repository, you can simply copy part of the directory structure that was pulled in by NPM:
|
||||
|
||||
This will build and publish the SDK, and all of its dependencies to the specified Maven repository (`/tmp/repo`) in
|
||||
this example.
|
||||
$ cp -r ../node_modules/react-native/android/com /tmp/repo/
|
||||
|
||||
In the same way, copy the JavaScriptCore dependency:
|
||||
|
||||
$ cp -r ../node_modules/jsc-android/dist/org /tmp/repo/
|
||||
|
||||
Alternatively, you can use the scripts located in the android/scripts directory to publish these dependencies to your Maven repo.
|
||||
|
||||
Third-party React Native _modules_, which Jitsi Meet SDK for Android depends on, are download by NPM in source code form. These need to be assembled into Maven artifacts, and then published to your local Maven repository. The SDK project facilitates this.
|
||||
|
||||
To prepare, Configure the Maven repositories in which you are going to publish the SDK artifacts/binaries. In `android/sdk/build.gradle` as well as in `android/build.gradle` modify the lines that contain:
|
||||
|
||||
"file:${rootProject.projectDir}/../../jitsi-maven-repository/releases"
|
||||
|
||||
Change this value (which represents the Maven repository location used internally by the Jitsi Developers) to the location of the repository that you'd like to use:
|
||||
|
||||
"file:/tmp/repo"
|
||||
|
||||
Make sure to do this in both files! Each file should require one line to be changed.
|
||||
|
||||
To prevent artifacts from previous builds affecting you're outcome, it's good to start with cleaning your work directories:
|
||||
|
||||
$ ./gradlew clean
|
||||
|
||||
To create the release assembly for any _specific_ third-party React Native module that you need, you can execture the following commands, replace the module name in the examples below.
|
||||
|
||||
$ ./gradlew :react-native-webrtc:assembleRelease
|
||||
$ ./gradlew :react-native-webrtc:publish
|
||||
|
||||
You build and publish the SDK itself in the same way:
|
||||
|
||||
$ ./gradlew :sdk:assembleRelease
|
||||
$ ./gradlew :sdk:publish
|
||||
|
||||
Alternatively, you can assemble and publish _all_ subprojects, which include the react-native modules, but also the SDK itself, with a single command:
|
||||
|
||||
$ ./gradlew clean assembleRelease publish
|
||||
|
||||
You're now ready to use the artifacts. In _your_ project, add the Maven repository that you used above (`/tmp/repo`) into your top-level `build.gradle` file:
|
||||
|
||||
@@ -82,8 +121,7 @@ Then, define the dependency `org.jitsi.react:jitsi-meet-sdk` into the `build.gra
|
||||
|
||||
implementation ('org.jitsi.react:jitsi-meet-sdk:+') { transitive = true }
|
||||
|
||||
Note that there should not be a need to explicitly add the other dependencies, as they will be pulled in as transitive
|
||||
dependencies of `jitsi-meet-sdk`.
|
||||
Note that there should not be a need to explicitly add the other dependencies, as they will be pulled in as transitive dependencies of `jitsi-meet-sdk`.
|
||||
|
||||
</details>
|
||||
|
||||
@@ -129,14 +167,14 @@ View is strongly recommended.
|
||||
package org.jitsi.example;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
import org.jitsi.meet.sdk.JitsiMeetView;
|
||||
import org.jitsi.meet.sdk.ReactActivityLifecycleCallbacks;
|
||||
|
||||
// Example
|
||||
//
|
||||
public class MainActivity extends FragmentActivity implements JitsiMeetActivityInterface {
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
private JitsiMeetView view;
|
||||
|
||||
@Override
|
||||
@@ -144,13 +182,13 @@ public class MainActivity extends FragmentActivity implements JitsiMeetActivityI
|
||||
int requestCode,
|
||||
int resultCode,
|
||||
Intent data) {
|
||||
JitsiMeetActivityDelegate.onActivityResult(
|
||||
ReactActivityLifecycleCallbacks.onActivityResult(
|
||||
this, requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
JitsiMeetActivityDelegate.onBackPressed();
|
||||
ReactActivityLifecycleCallbacks.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -158,10 +196,7 @@ public class MainActivity extends FragmentActivity implements JitsiMeetActivityI
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
view = new JitsiMeetView(this);
|
||||
JitsiMeetConferenceOptions options = new JitsiMeetConferenceOptions.Builder()
|
||||
.setRoom("https://meet.jit.si/test123")
|
||||
.build();
|
||||
view.join(options);
|
||||
view.loadURL(null);
|
||||
|
||||
setContentView(view);
|
||||
}
|
||||
@@ -173,12 +208,12 @@ public class MainActivity extends FragmentActivity implements JitsiMeetActivityI
|
||||
view.dispose();
|
||||
view = null;
|
||||
|
||||
JitsiMeetActivityDelegate.onHostDestroy(this);
|
||||
ReactActivityLifecycleCallbacks.onHostDestroy(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
JitsiMeetActivityDelegate.onNewIntent(intent);
|
||||
ReactActivityLifecycleCallbacks.onNewIntent(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -186,85 +221,144 @@ public class MainActivity extends FragmentActivity implements JitsiMeetActivityI
|
||||
final int requestCode,
|
||||
final String[] permissions,
|
||||
final int[] grantResults) {
|
||||
JitsiMeetActivityDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
ReactActivityLifecycleCallbacks.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
JitsiMeetActivityDelegate.onHostResume(this);
|
||||
ReactActivityLifecycleCallbacks.onHostResume(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
||||
JitsiMeetActivityDelegate.onHostPause(this);
|
||||
ReactActivityLifecycleCallbacks.onHostPause(this);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</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`
|
||||
which displays a single `JitsiMeetView`. You can pass a URL as a `ACTION_VIEW`
|
||||
on the Intent when starting it and it will join the conference, and will be
|
||||
automatically terminated (finish() will be called on the activity) when the
|
||||
conference ends or fails.
|
||||
This class encapsulates a high level API in the form of an Android `Activity`
|
||||
which displays a single `JitsiMeetView`.
|
||||
|
||||
### JitsiMeetView
|
||||
|
||||
The `JitsiMeetView` class is the core of Jitsi Meet SDK. It's designed to
|
||||
display a Jitsi Meet conference (or a welcome page).
|
||||
|
||||
#### join(options)
|
||||
|
||||
Joins the conference specified by the given `JitsiMeetConferenceOptions`.
|
||||
|
||||
#### leave()
|
||||
|
||||
Leaves the currently active conference. If the welcome page is enabled it will
|
||||
go back to it, otherwise a black window will be shown.
|
||||
|
||||
#### dispose()
|
||||
|
||||
Releases all resources associated with this view. This method MUST be called
|
||||
when the Activity holding this view is going to be destroyed, usually in the
|
||||
`onDestroy()` method.
|
||||
|
||||
#### getDefaultURL()
|
||||
|
||||
Returns the default base URL used to join a conference when a partial URL (e.g.
|
||||
a room name only) is specified to `loadURLString`/`loadURLObject`. If not set or
|
||||
if set to `null`, the default built in JavaScript is used: https://meet.jit.si.
|
||||
|
||||
#### getListener()
|
||||
|
||||
Returns the `JitsiMeetViewListener` instance attached to the view.
|
||||
|
||||
#### isPictureInPictureEnabled()
|
||||
|
||||
Returns `true` if Picture-in-Picture is enabled; `false`, otherwise. If not
|
||||
explicitly set (by a preceding `setPictureInPictureEnabled` call), defaults to
|
||||
`true` if the platform supports Picture-in-Picture natively; `false`, otherwise.
|
||||
|
||||
#### isWelcomePageEnabled()
|
||||
|
||||
Returns true if the Welcome page is enabled; otherwise, false. If false, a black
|
||||
empty view will be rendered when not in a conference. Defaults to false.
|
||||
|
||||
#### loadURL(URL)
|
||||
|
||||
Loads a specific URL which may identify a conference to join. If the specified
|
||||
URL is null and the Welcome page is enabled, the Welcome page is displayed
|
||||
instead.
|
||||
|
||||
#### loadURLString(String)
|
||||
|
||||
Loads a specific URL which may identify a conference to join. If the specified
|
||||
URL is null and the Welcome page is enabled, the Welcome page is displayed
|
||||
instead.
|
||||
|
||||
#### loadURLObject(Bundle)
|
||||
|
||||
Loads a specific URL which may identify a conference to join. The URL is
|
||||
specified in the form of a Bundle of properties which (1) internally are
|
||||
sufficient to construct a URL (string) while (2) abstracting the specifics of
|
||||
constructing the URL away from API clients/consumers. If the specified URL is
|
||||
null and the Welcome page is enabled, the Welcome page is displayed instead.
|
||||
|
||||
Example:
|
||||
|
||||
```java
|
||||
Bundle config = new Bundle();
|
||||
config.putBoolean("startWithAudioMuted", true);
|
||||
config.putBoolean("startWithVideoMuted", false);
|
||||
Bundle urlObject = new Bundle();
|
||||
urlObject.putBundle("config", config);
|
||||
urlObject.putString("url", "https://meet.jit.si/Test123");
|
||||
view.loadURLObject(urlObject);
|
||||
```
|
||||
|
||||
#### setDefaultURL(URL)
|
||||
|
||||
Sets the default URL. See `getDefaultURL` for more information.
|
||||
|
||||
NOTE: Must be called before (if at all) `loadURL`/`loadURLString` for it to take
|
||||
effect.
|
||||
|
||||
#### setListener(listener)
|
||||
|
||||
Sets the given listener (class implementing the `JitsiMeetViewListener`
|
||||
interface) on the view.
|
||||
|
||||
### JitsiMeetConferenceOptions
|
||||
#### setPictureInPictureEnabled(boolean)
|
||||
|
||||
This object encapsulates all the options that can be tweaked when joining
|
||||
a conference.
|
||||
Sets whether Picture-in-Picture is enabled. If not set, Jitsi Meet SDK
|
||||
automatically enables/disables Picture-in-Picture based on native platform
|
||||
support.
|
||||
|
||||
Example:
|
||||
NOTE: Must be called (if at all) before `loadURL`/`loadURLString` for it to take
|
||||
effect.
|
||||
|
||||
```java
|
||||
JitsiMeetConferenceOptions options = new JitsiMeetConferenceOptions.Builder()
|
||||
.setServerURL(new URL("https://meet.jit.si"))
|
||||
.setRoom("test123")
|
||||
.setAudioMuted(false)
|
||||
.setVideoMuted(false)
|
||||
.setAudioOnly(false)
|
||||
.setWelcomePageEnabled(false)
|
||||
.build();
|
||||
```
|
||||
#### setWelcomePageEnabled(boolean)
|
||||
|
||||
See the `JitsiMeetConferenceOptions` implementation for all available options.
|
||||
Sets whether the Welcome page is enabled. See `isWelcomePageEnabled` for more
|
||||
information.
|
||||
|
||||
### JitsiMeetActivityDelegate
|
||||
NOTE: Must be called (if at all) before `loadURL`/`loadURLString` for it to take
|
||||
effect.
|
||||
|
||||
### ReactActivityLifecycleCallbacks
|
||||
|
||||
This class handles the interaction between `JitsiMeetView` and its enclosing
|
||||
`Activity`. Generally this shouldn't be consumed by users, because they'd be
|
||||
@@ -320,20 +414,29 @@ This is a static method.
|
||||
`JitsiMeetViewListener` provides an interface apps can implement to listen to
|
||||
the state of the Jitsi Meet conference displayed in a `JitsiMeetView`.
|
||||
|
||||
`JitsiMeetViewAdapter`, a default implementation of the
|
||||
`JitsiMeetViewListener` interface is also provided. Apps may extend the class
|
||||
instead of implementing the interface in order to minimize boilerplate.
|
||||
|
||||
##### onConferenceFailed
|
||||
|
||||
Called when a joining a conference was unsuccessful or when there was an error
|
||||
while in a conference.
|
||||
|
||||
The `data` `Map` contains an "error" key describing the error and a "url" key
|
||||
with the conference URL.
|
||||
|
||||
#### onConferenceJoined
|
||||
|
||||
Called when a conference was joined.
|
||||
|
||||
The `data` `Map` contains a "url" key with the conference URL.
|
||||
|
||||
#### onConferenceTerminated
|
||||
#### onConferenceLeft
|
||||
|
||||
Called when a conference was terminated either by user choice or due to a
|
||||
failure.
|
||||
Called when a conference was left.
|
||||
|
||||
The `data` `Map` contains an "error" key with the error and a "url" key
|
||||
with the conference URL. If the conference finished gracefully no `error`
|
||||
key will be present.
|
||||
The `data` `Map` contains a "url" key with the conference URL.
|
||||
|
||||
#### onConferenceWillJoin
|
||||
|
||||
@@ -341,6 +444,20 @@ Called before a conference is joined.
|
||||
|
||||
The `data` `Map` contains a "url" key with the conference URL.
|
||||
|
||||
#### onConferenceWillLeave
|
||||
|
||||
Called before a conference is left.
|
||||
|
||||
The `data` `Map` contains a "url" key with the conference URL.
|
||||
|
||||
#### onLoadConfigError
|
||||
|
||||
Called when loading the main configuration file from the Jitsi Meet deployment
|
||||
fails.
|
||||
|
||||
The `data` `Map` contains an "error" key with the error and a "url" key with the
|
||||
conference URL which necessitated the loading of the configuration file.
|
||||
|
||||
## ProGuard rules
|
||||
|
||||
When using the SDK on a project some proguard rules have to be added in order
|
||||
@@ -353,6 +470,10 @@ rules file: https://github.com/jitsi/jitsi-meet/blob/master/android/app/proguard
|
||||
Picture-in-Picture style scenario, in a rectangle too small to accommodate its
|
||||
"full" UI.
|
||||
|
||||
Jitsi Meet SDK automatically enables (unless explicitly disabled by a
|
||||
`setPictureInPictureEnabled(false)` call) Android's native Picture-in-Picture
|
||||
mode iff the platform is supported i.e. Android >= Oreo.
|
||||
|
||||
## Dropbox integration
|
||||
|
||||
To setup the Dropbox integration, follow these steps:
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -1,2 +0,0 @@
|
||||
connection.project.dir=..
|
||||
eclipse.preferences.version=1
|
||||
@@ -1,7 +1,6 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
boolean googleServicesEnabled \
|
||||
= project.file('google-services.json').exists() && !rootProject.ext.libreBuild
|
||||
boolean googleServicesEnabled = project.file('google-services.json').exists()
|
||||
|
||||
// Crashlytics integration is done as part of Firebase now, so it gets
|
||||
// automagically activated with google-services.json
|
||||
@@ -9,11 +8,6 @@ if (googleServicesEnabled) {
|
||||
apply plugin: 'io.fabric'
|
||||
}
|
||||
|
||||
// Use the number of seconds/10 since Jan 1 2019 as the versionCode.
|
||||
// This lets us upload a new build at most every 10 seconds for the
|
||||
// next ~680 years.
|
||||
// https://stackoverflow.com/a/38643838
|
||||
def vcode = (int)(((new Date().getTime()/1000) - 1546297200) / 10)
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
@@ -21,49 +15,37 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'org.jitsi.meet'
|
||||
versionCode vcode
|
||||
versionCode Integer.parseInt(project.buildNumber)
|
||||
versionName project.appVersion
|
||||
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
|
||||
ndk {
|
||||
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||
abiFilters 'armeabi-v7a', 'x86'
|
||||
}
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
debug {
|
||||
storeFile file('debug.keystore')
|
||||
storePassword 'android'
|
||||
keyAlias 'androiddebugkey'
|
||||
keyPassword 'android'
|
||||
packagingOptions {
|
||||
// The project react-native does not provide 64-bit binaries at the
|
||||
// time of this writing. Unfortunately, packaging any 64-bit
|
||||
// binaries into the .apk will crash the app at runtime on 64-bit
|
||||
// platforms.
|
||||
exclude '/lib/mips64/**'
|
||||
exclude '/lib/arm64-v8a/**'
|
||||
exclude '/lib/x86_64/**'
|
||||
}
|
||||
}
|
||||
|
||||
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}"
|
||||
buildConfigField "boolean", "LIBRE_BUILD", "${rootProject.ext.libreBuild}"
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
if (rootProject.ext.libreBuild) {
|
||||
srcDir "src"
|
||||
exclude "**/GoogleServicesHelper.java"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,23 +61,30 @@ 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'
|
||||
|
||||
if (!rootProject.ext.libreBuild) {
|
||||
implementation 'com.google.android.gms:play-services-auth:16.0.1'
|
||||
|
||||
// Firebase
|
||||
// - Crashlytics
|
||||
// - Dynamic Links
|
||||
implementation 'com.google.firebase:firebase-core:16.0.6'
|
||||
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.8'
|
||||
implementation 'com.google.firebase:firebase-dynamic-links:16.1.5'
|
||||
}
|
||||
implementation "com.android.support:support-v4:${rootProject.ext.supportLibVersion}"
|
||||
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
|
||||
implementation 'com.google.android.gms:play-services-auth:16.0.1'
|
||||
|
||||
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}"
|
||||
|
||||
// Firebase
|
||||
// - Crashlytics
|
||||
// - Dynamic Links
|
||||
implementation 'com.google.firebase:firebase-core:16.0.6'
|
||||
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.8'
|
||||
implementation 'com.google.firebase:firebase-dynamic-links:16.1.5'
|
||||
}
|
||||
|
||||
gradle.projectsEvaluated {
|
||||
@@ -125,53 +114,21 @@ gradle.projectsEvaluated {
|
||||
|
||||
android.applicationVariants.all { variant ->
|
||||
variant.outputs.each { output ->
|
||||
output.getProcessManifestProvider().get().doLast {
|
||||
def outputDir = manifestOutputDirectory.get().asFile
|
||||
def manifestPath = new File(outputDir, 'AndroidManifest.xml')
|
||||
def charset = 'UTF-8'
|
||||
def text
|
||||
text = manifestPath.getText(charset)
|
||||
text = text.replace('</application>', "${dropboxActivity}</application>")
|
||||
manifestPath.write(text, charset)
|
||||
output.processManifest.doLast {
|
||||
def f = new File(manifestOutputDirectory, 'AndroidManifest.xml')
|
||||
if (!f.isFile()) {
|
||||
f = new File(new File(manifestOutputDirectory, output.dirName), 'AndroidManifest.xml')
|
||||
}
|
||||
if (f.exists()) {
|
||||
def charset = 'UTF-8'
|
||||
def s = f.getText(charset)
|
||||
s = s.replace('</application>', "${dropboxActivity}</application>")
|
||||
f.write(s, charset)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run React packager
|
||||
android.applicationVariants.all { variant ->
|
||||
def targetName = variant.name.capitalize()
|
||||
|
||||
def currentRunPackagerTask = tasks.create(
|
||||
name: "run${targetName}ReactPackager",
|
||||
type: Exec) {
|
||||
group = "react"
|
||||
description = "Run the React packager."
|
||||
|
||||
doFirst {
|
||||
println "Starting the React packager..."
|
||||
|
||||
def androidRoot = file("${projectDir}/../")
|
||||
|
||||
// Set up the call to the script
|
||||
workingDir androidRoot
|
||||
|
||||
// Run the packager
|
||||
commandLine("scripts/run-packager.sh")
|
||||
}
|
||||
|
||||
// Set up dev mode
|
||||
def devEnabled = !targetName.toLowerCase().contains("release")
|
||||
|
||||
// Only enable for dev builds
|
||||
enabled devEnabled
|
||||
}
|
||||
|
||||
def packageTask = variant.packageApplicationProvider.get()
|
||||
|
||||
packageTask.dependsOn(currentRunPackagerTask)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (googleServicesEnabled) {
|
||||
|
||||
5
android/app/proguard-rules-debug.pro
Normal 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
|
||||
27
android/app/proguard-rules.pro
vendored
@@ -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.** { *; }
|
||||
@@ -63,7 +80,6 @@
|
||||
|
||||
# Jisti Meet SDK
|
||||
|
||||
-keep class org.jitsi.meet.** { *; }
|
||||
-keep class org.jitsi.meet.sdk.** { *; }
|
||||
|
||||
# We added the following when we switched minifyEnabled on. Probably because we
|
||||
@@ -83,10 +99,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.** { *; }
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -24,7 +25,6 @@
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:host="alpha.jitsi.net" android:scheme="https" />
|
||||
<data android:host="beta.meet.jit.si" android:scheme="https" />
|
||||
<data android:host="meet.jit.si" android:scheme="https" />
|
||||
</intent-filter>
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
package org.jitsi.meet;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import com.crashlytics.android.Crashlytics;
|
||||
import com.google.firebase.dynamiclinks.FirebaseDynamicLinks;
|
||||
import io.fabric.sdk.android.Fabric;
|
||||
|
||||
import org.jitsi.meet.sdk.JitsiMeetActivity;
|
||||
|
||||
/**
|
||||
* Helper class to initialize Google related services and functionality.
|
||||
* This functionality is compiled conditionally and called via reflection, that's why it was
|
||||
* extracted here.
|
||||
*
|
||||
* "Libre builds" (builds with the LIBRE_BUILD flag set) will not include this file.
|
||||
*/
|
||||
final class GoogleServicesHelper {
|
||||
public static void initialize(JitsiMeetActivity activity) {
|
||||
if (BuildConfig.GOOGLE_SERVICES_ENABLED) {
|
||||
Log.d(activity.getClass().getSimpleName(), "Initializing Google Services");
|
||||
|
||||
Fabric.with(activity, new Crashlytics());
|
||||
|
||||
FirebaseDynamicLinks.getInstance().getDynamicLink(activity.getIntent())
|
||||
.addOnSuccessListener(activity, pendingDynamicLinkData -> {
|
||||
Uri dynamicLink = null;
|
||||
|
||||
if (pendingDynamicLinkData != null) {
|
||||
dynamicLink = pendingDynamicLinkData.getLink();
|
||||
}
|
||||
|
||||
if (dynamicLink != null) {
|
||||
activity.join(dynamicLink.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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.
|
||||
@@ -16,135 +17,243 @@
|
||||
|
||||
package org.jitsi.meet;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.Settings;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.jitsi.meet.sdk.JitsiMeet;
|
||||
import org.jitsi.meet.sdk.JitsiMeetActivity;
|
||||
import org.jitsi.meet.sdk.JitsiMeetConferenceOptions;
|
||||
import org.jitsi.meet.sdk.JitsiMeetView;
|
||||
import org.jitsi.meet.sdk.JitsiMeetViewListener;
|
||||
import org.jitsi.meet.sdk.invite.AddPeopleController;
|
||||
import org.jitsi.meet.sdk.invite.AddPeopleControllerListener;
|
||||
import org.jitsi.meet.sdk.invite.InviteController;
|
||||
import org.jitsi.meet.sdk.invite.InviteControllerListener;
|
||||
|
||||
import com.crashlytics.android.Crashlytics;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
import com.google.firebase.dynamiclinks.FirebaseDynamicLinks;
|
||||
import io.fabric.sdk.android.Fabric;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The one and only Activity that the Jitsi Meet app needs. The
|
||||
* The one and only {@link Activity} that the Jitsi Meet app needs. The
|
||||
* {@code Activity} is launched in {@code singleTask} mode, so it will be
|
||||
* created upon application initialization and there will be a single instance
|
||||
* of it. Further attempts at launching the application once it was already
|
||||
* launched will result in {@link MainActivity#onNewIntent(Intent)} being called.
|
||||
* launched will result in {@link Activity#onNewIntent(Intent)} being called.
|
||||
*
|
||||
* This {@code Activity} extends {@link JitsiMeetActivity} to keep the React
|
||||
* Native CLI working, since the latter always tries to launch an
|
||||
* {@code Activity} named {@code MainActivity} when doing
|
||||
* {@code react-native run-android}.
|
||||
*/
|
||||
public class MainActivity extends JitsiMeetActivity {
|
||||
/**
|
||||
* The request code identifying requests for the permission to draw on top
|
||||
* of other apps. The value must be 16-bit and is arbitrarily chosen here.
|
||||
* The query to perform through {@link AddPeopleController} when the
|
||||
* {@code InviteButton} is tapped in order to exercise the public API of the
|
||||
* feature invite. If {@code null}, the {@code InviteButton} will not be
|
||||
* rendered.
|
||||
*/
|
||||
private static final int OVERLAY_PERMISSION_REQUEST_CODE
|
||||
= (int) (Math.random() * Short.MAX_VALUE);
|
||||
|
||||
// JitsiMeetActivity overrides
|
||||
//
|
||||
private static final String ADD_PEOPLE_CONTROLLER_QUERY = null;
|
||||
|
||||
@Override
|
||||
protected boolean extraInitialize() {
|
||||
Log.d(this.getClass().getSimpleName(), "LIBRE_BUILD="+BuildConfig.LIBRE_BUILD);
|
||||
protected JitsiMeetView initializeView() {
|
||||
JitsiMeetView view = super.initializeView();
|
||||
|
||||
// XXX In order to increase (1) awareness of API breakages and (2) API
|
||||
// coverage, utilize JitsiMeetViewListener in the Debug configuration of
|
||||
// the app.
|
||||
if (BuildConfig.DEBUG && view != null) {
|
||||
view.setListener(new JitsiMeetViewListener() {
|
||||
private void on(String name, Map<String, Object> data) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
// Log with the tag "ReactNative" in order to have the log
|
||||
// visible in react-native log-android as well.
|
||||
Log.d(
|
||||
"ReactNative",
|
||||
JitsiMeetViewListener.class.getSimpleName() + " "
|
||||
+ name + " "
|
||||
+ data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConferenceFailed(Map<String, Object> data) {
|
||||
on("CONFERENCE_FAILED", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConferenceJoined(Map<String, Object> data) {
|
||||
on("CONFERENCE_JOINED", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConferenceLeft(Map<String, Object> data) {
|
||||
on("CONFERENCE_LEFT", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConferenceWillJoin(Map<String, Object> data) {
|
||||
on("CONFERENCE_WILL_JOIN", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConferenceWillLeave(Map<String, Object> data) {
|
||||
on("CONFERENCE_WILL_LEAVE", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadConfigError(Map<String, Object> data) {
|
||||
on("LOAD_CONFIG_ERROR", data);
|
||||
}
|
||||
});
|
||||
|
||||
// inviteController
|
||||
final InviteController inviteController
|
||||
= view.getInviteController();
|
||||
|
||||
inviteController.setListener(new InviteControllerListener() {
|
||||
public void beginAddPeople(
|
||||
AddPeopleController addPeopleController) {
|
||||
onInviteControllerBeginAddPeople(
|
||||
inviteController,
|
||||
addPeopleController);
|
||||
}
|
||||
});
|
||||
inviteController.setAddPeopleEnabled(
|
||||
ADD_PEOPLE_CONTROLLER_QUERY != null);
|
||||
inviteController.setDialOutEnabled(
|
||||
inviteController.isAddPeopleEnabled());
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private void onAddPeopleControllerInviteSettled(
|
||||
AddPeopleController addPeopleController,
|
||||
List<Map<String, Object>> failedInvitees) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
// XXX Explicitly invoke endAddPeople on addPeopleController; otherwise,
|
||||
// it is going to be memory-leaked in the associated InviteController
|
||||
// and no subsequent InviteButton clicks/taps will be delivered.
|
||||
// Technically, endAddPeople will automatically be invoked if there are
|
||||
// no failedInviteees i.e. the invite succeeeded for all specified
|
||||
// invitees.
|
||||
addPeopleController.endAddPeople();
|
||||
}
|
||||
|
||||
private void onAddPeopleControllerReceivedResults(
|
||||
AddPeopleController addPeopleController,
|
||||
List<Map<String, Object>> results,
|
||||
String query) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
int size = results.size();
|
||||
|
||||
if (size > 0) {
|
||||
// Exercise AddPeopleController's inviteById implementation.
|
||||
List<String> ids = new ArrayList<>(size);
|
||||
|
||||
for (Map<String, Object> result : results) {
|
||||
Object id = result.get("id");
|
||||
|
||||
if (id != null) {
|
||||
ids.add(id.toString());
|
||||
}
|
||||
}
|
||||
|
||||
addPeopleController.inviteById(ids);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// XXX Explicitly invoke endAddPeople on addPeopleController; otherwise,
|
||||
// it is going to be memory-leaked in the associated InviteController
|
||||
// and no subsequent InviteButton clicks/taps will be delivered.
|
||||
addPeopleController.endAddPeople();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
// As this is the Jitsi Meet app (i.e. not the Jitsi Meet SDK), we do
|
||||
// want to enable some options.
|
||||
|
||||
// The welcome page defaults to disabled in the SDK at the time of this
|
||||
// writing but it is clearer to be explicit about what we want anyway.
|
||||
setWelcomePageEnabled(true);
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Setup Crashlytics and Firebase Dynamic Links
|
||||
// Here we are using reflection since it may have been disabled at compile time.
|
||||
try {
|
||||
Class<?> cls = Class.forName("org.jitsi.meet.GoogleServicesHelper");
|
||||
Method m = cls.getMethod("initialize", JitsiMeetActivity.class);
|
||||
m.invoke(null, this);
|
||||
} catch (Exception e) {
|
||||
// Ignore any error, the module is not compiled when LIBRE_BUILD is enabled.
|
||||
}
|
||||
if (BuildConfig.GOOGLE_SERVICES_ENABLED) {
|
||||
Fabric.with(this, new Crashlytics());
|
||||
|
||||
// In Debug builds React needs permission to write over other apps in
|
||||
// order to display the warning and error overlays.
|
||||
if (BuildConfig.DEBUG) {
|
||||
if (canRequestOverlayPermission() && !Settings.canDrawOverlays(this)) {
|
||||
Intent intent
|
||||
= new Intent(
|
||||
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
|
||||
Uri.parse("package:" + getPackageName()));
|
||||
FirebaseDynamicLinks.getInstance().getDynamicLink(getIntent())
|
||||
.addOnSuccessListener(this, pendingDynamicLinkData -> {
|
||||
Uri dynamicLink = null;
|
||||
|
||||
startActivityForResult(intent, OVERLAY_PERMISSION_REQUEST_CODE);
|
||||
if (pendingDynamicLinkData != null) {
|
||||
dynamicLink = pendingDynamicLinkData.getLink();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize() {
|
||||
// Set default options
|
||||
JitsiMeetConferenceOptions defaultOptions
|
||||
= new JitsiMeetConferenceOptions.Builder()
|
||||
.setWelcomePageEnabled(true)
|
||||
.setServerURL(buildURL("https://meet.jit.si"))
|
||||
.setFeatureFlag("call-integration.enabled", false)
|
||||
.build();
|
||||
JitsiMeet.setDefaultConferenceOptions(defaultOptions);
|
||||
|
||||
super.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConferenceTerminated(Map<String, Object> data) {
|
||||
Log.d(TAG, "Conference terminated: " + data);
|
||||
}
|
||||
|
||||
// Activity lifecycle method overrides
|
||||
//
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == OVERLAY_PERMISSION_REQUEST_CODE
|
||||
&& canRequestOverlayPermission()) {
|
||||
if (Settings.canDrawOverlays(this)) {
|
||||
initialize();
|
||||
return;
|
||||
}
|
||||
|
||||
throw new RuntimeException("Overlay permission is required when running in Debug mode.");
|
||||
}
|
||||
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
// ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
if (BuildConfig.DEBUG && keyCode == KeyEvent.KEYCODE_MENU) {
|
||||
JitsiMeet.showDevOptions();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
//
|
||||
|
||||
private @Nullable URL buildURL(String urlStr) {
|
||||
try {
|
||||
return new URL(urlStr);
|
||||
} catch (MalformedURLException e) {
|
||||
return null;
|
||||
if (dynamicLink != null) {
|
||||
try {
|
||||
loadURL(new URL(dynamicLink.toString()));
|
||||
} catch (MalformedURLException e) {
|
||||
Log.d("ReactNative", "Malformed dynamic link", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canRequestOverlayPermission() {
|
||||
return
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
&& getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.M;
|
||||
private void onInviteControllerBeginAddPeople(
|
||||
InviteController inviteController,
|
||||
AddPeopleController addPeopleController) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
// Log with the tag "ReactNative" in order to have the log visible in
|
||||
// react-native log-android as well.
|
||||
Log.d(
|
||||
"ReactNative",
|
||||
InviteControllerListener.class.getSimpleName() + ".beginAddPeople");
|
||||
|
||||
String query = ADD_PEOPLE_CONTROLLER_QUERY;
|
||||
|
||||
if (query != null
|
||||
&& (inviteController.isAddPeopleEnabled()
|
||||
|| inviteController.isDialOutEnabled())) {
|
||||
addPeopleController.setListener(new AddPeopleControllerListener() {
|
||||
public void onInviteSettled(
|
||||
AddPeopleController addPeopleController,
|
||||
List<Map<String, Object>> failedInvitees) {
|
||||
onAddPeopleControllerInviteSettled(
|
||||
addPeopleController,
|
||||
failedInvitees);
|
||||
}
|
||||
|
||||
public void onReceivedResults(
|
||||
AddPeopleController addPeopleController,
|
||||
List<Map<String, Object>> results,
|
||||
String query) {
|
||||
onAddPeopleControllerReceivedResults(
|
||||
addPeopleController,
|
||||
results, query);
|
||||
}
|
||||
});
|
||||
addPeopleController.performQuery(query);
|
||||
} else {
|
||||
// XXX Explicitly invoke endAddPeople on addPeopleController;
|
||||
// otherwise, it is going to be memory-leaked in the associated
|
||||
// InviteController and no subsequent InviteButton clicks/taps will
|
||||
// be delivered.
|
||||
addPeopleController.endAddPeople();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,13 +14,23 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <React/RCTBridge.h>
|
||||
package org.jitsi.meet;
|
||||
|
||||
#import "JitsiMeet.h"
|
||||
import android.app.Application;
|
||||
|
||||
@interface JitsiMeet ()
|
||||
import com.squareup.leakcanary.LeakCanary;
|
||||
|
||||
- (NSDictionary *)getDefaultProps;
|
||||
- (RCTBridge *)getReactBridge;
|
||||
/**
|
||||
* Simple {@link Application} for hooking up LeakCanary:
|
||||
* https://github.com/square/leakcanary
|
||||
*/
|
||||
public class MainApplication extends Application {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
@end
|
||||
if (!LeakCanary.isInAnalyzerProcess(this)) {
|
||||
LeakCanary.install(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 659 B |
|
Before Width: | Height: | Size: 379 B |
|
Before Width: | Height: | Size: 960 B |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
@@ -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>
|
||||
@@ -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>
|
||||
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#66A8DD</color>
|
||||
</resources>
|
||||
@@ -2,6 +2,5 @@
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="android:navigationBarColor">#1081B2</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -12,9 +12,9 @@ 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.android.tools.build:gradle:3.2.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,9 @@ allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
// React Native (JS, Obj-C sources, Android binaries) is installed from npm.
|
||||
maven { url "$rootDir/../node_modules/jsc-android/dist" }
|
||||
// React Native (JS, Obj-C sources, Android binaries) is installed from
|
||||
// npm.
|
||||
maven { url "$rootDir/../node_modules/react-native/android" }
|
||||
}
|
||||
|
||||
@@ -40,6 +42,12 @@ allprojects {
|
||||
def version = new JsonSlurper().parseText(file.text).version
|
||||
details.useVersion version
|
||||
}
|
||||
if (details.requested.group == 'org.webkit'
|
||||
&& details.requested.name == 'android-jsc') {
|
||||
def file = new File("$rootDir/../node_modules/jsc-android/package.json")
|
||||
def version = new JsonSlurper().parseText(file.text).version
|
||||
details.useVersion "r${version.tokenize('.')[0]}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,41 +62,66 @@ allprojects {
|
||||
publishing {
|
||||
publications {}
|
||||
repositories {
|
||||
maven {
|
||||
url rootProject.ext.mavenRepo
|
||||
if (!rootProject.ext.mavenRepo.startsWith("file")) {
|
||||
credentials {
|
||||
username rootProject.ext.mavenUser
|
||||
password rootProject.ext.mavenPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
maven { url "file:${rootProject.projectDir}/../../jitsi-maven-repository/releases" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use the number of seconds/10 since Jan 1 2019 as the version qualifier number.
|
||||
// This will last for the next ~680 years.
|
||||
// https://stackoverflow.com/a/38643838
|
||||
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)
|
||||
|
||||
// Release every dependency the SDK has with a -jitsi-XXX qualified version. This allows
|
||||
// us to pin the dependencies and make sure they are always updated, no matter what.
|
||||
// React Native modules have an npm peer dependency on react-native,
|
||||
// they do not have an npm dependency on it. Further below though we
|
||||
// choose a react-native version (range) when we represent them as
|
||||
// Maven artifacts. Effectively, we are forking the projects by not
|
||||
// complying with the full range of their npm peer dependency and,
|
||||
// consequently, we should qualify their version.
|
||||
def versionQualifier = '-jitsi-1'
|
||||
if ('react-native-background-timer' == project.name)
|
||||
versionQualifier = '-jitsi-3' // 2.0.0 + react-native 0.57
|
||||
else if ('react-native-calendar-events' == project.name)
|
||||
versionQualifier = '-jitsi-2' // 1.6.4 + react-native 0.57
|
||||
else if ('react-native-fast-image' == project.name)
|
||||
versionQualifier = '-jitsi-2' // 5.1.1 + react-native 0.57
|
||||
else if ('react-native-google-signin' == project.name)
|
||||
versionQualifier = '-jitsi-2' // 1.0.2 + react-native 0.57
|
||||
else if ('react-native-immersive' == project.name)
|
||||
versionQualifier = '-jitsi-5' // 2.0.0 + react-native 0.57
|
||||
else if ('react-native-keep-awake' == project.name)
|
||||
versionQualifier = '-jitsi-4' // 4.0.0 + react-native 0.57
|
||||
else if ('react-native-linear-gradient' == project.name)
|
||||
versionQualifier = '-jitsi-1' // 2.4.0 + react-native 0.57
|
||||
else if ('react-native-sound' == project.name)
|
||||
versionQualifier = '-jitsi-2' // 0.10.9 + react-native 0.57
|
||||
else if ('react-native-vector-icons' == project.name)
|
||||
versionQualifier = '-jitsi-3' // 6.0.2 + react-native 0.57
|
||||
else if ('react-native-webrtc' == project.name)
|
||||
versionQualifier = '-jitsi-9' // 6322a9b5a38ce590cfaea4041072ea87c8dbf558 + react-native 0.57
|
||||
|
||||
project.version = "${json.version}-jitsi-${versionQualifierNumber}"
|
||||
project.version = "${json.version}${versionQualifier}"
|
||||
|
||||
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 androidJavadocs(type: Javadoc) {
|
||||
source = android.sourceSets.main.java.source
|
||||
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
|
||||
failOnError false
|
||||
}
|
||||
task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
|
||||
classifier = 'javadoc'
|
||||
from androidJavadocs.destinationDir
|
||||
}
|
||||
task androidSourcesJar(type: Jar) {
|
||||
classifier = 'sources'
|
||||
from android.sourceSets.main.java.source
|
||||
@@ -104,6 +137,7 @@ allprojects {
|
||||
extension "aar"
|
||||
}
|
||||
artifact(androidSourcesJar)
|
||||
artifact(androidJavadocsJar)
|
||||
pom.withXml {
|
||||
def pomXml = asNode()
|
||||
pomXml.appendNode('name', project.name)
|
||||
@@ -154,13 +188,53 @@ ext {
|
||||
// of ours.
|
||||
moduleGroupId = 'com.facebook.react'
|
||||
|
||||
// Maven repo where artifacts will be published
|
||||
mavenRepo = System.env.MVN_REPO ?: ""
|
||||
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()
|
||||
// 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
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
json_key_file("")
|
||||
package_name("org.jitsi.meet")
|
||||
@@ -1,34 +0,0 @@
|
||||
ENV["FASTLANE_SKIP_UPDATE_CHECK"] = "1"
|
||||
opt_out_usage
|
||||
|
||||
default_platform(:android)
|
||||
|
||||
platform :android do
|
||||
desc "Deploy a new version to Goolge Play (Closed Beta)"
|
||||
lane :deploy do
|
||||
# Cleanup
|
||||
gradle(task: "clean")
|
||||
|
||||
# Build and sign the app
|
||||
gradle(
|
||||
task: "assemble",
|
||||
build_type: "Release",
|
||||
print_command: false,
|
||||
properties: {
|
||||
"android.injected.signing.store.file" => ENV["JITSI_KEYSTORE"],
|
||||
"android.injected.signing.store.password" => ENV["JITSI_KEYSTORE_PASSWORD"],
|
||||
"android.injected.signing.key.alias" => ENV["JITSI_KEY_ALIAS"],
|
||||
"android.injected.signing.key.password" => ENV["JITSI_KEY_PASSWORD"],
|
||||
}
|
||||
)
|
||||
|
||||
# Upload built artifact to the Closed Beta track
|
||||
upload_to_play_store(
|
||||
track: "beta",
|
||||
json_key: ENV["JITSI_JSON_KEY_FILE"],
|
||||
skip_upload_metadata: true,
|
||||
skip_upload_images: true,
|
||||
skip_upload_screenshots: true
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -1,29 +0,0 @@
|
||||
fastlane documentation
|
||||
================
|
||||
# Installation
|
||||
|
||||
Make sure you have the latest version of the Xcode command line tools installed:
|
||||
|
||||
```
|
||||
xcode-select --install
|
||||
```
|
||||
|
||||
Install _fastlane_ using
|
||||
```
|
||||
[sudo] gem install fastlane -NV
|
||||
```
|
||||
or alternatively using `brew cask install fastlane`
|
||||
|
||||
# Available Actions
|
||||
## Android
|
||||
### android deploy
|
||||
```
|
||||
fastlane android deploy
|
||||
```
|
||||
Deploy a new version to Goolge Play (Closed Beta)
|
||||
|
||||
----
|
||||
|
||||
This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run.
|
||||
More information about fastlane can be found on [fastlane.tools](https://fastlane.tools).
|
||||
The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
|
||||
|
Before Width: | Height: | Size: 342 KiB |
|
Before Width: | Height: | Size: 264 KiB |
|
Before Width: | Height: | Size: 164 KiB |
|
Before Width: | Height: | Size: 174 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 2.1 MiB |
|
Before Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 6.0 MiB |
|
Before Width: | Height: | Size: 3.1 MiB |
|
Before Width: | Height: | Size: 4.9 MiB |
|
Before Width: | Height: | Size: 694 KiB |
|
Before Width: | Height: | Size: 716 KiB |
|
Before Width: | Height: | Size: 950 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1000 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
@@ -17,8 +17,6 @@
|
||||
# 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.3.0
|
||||
sdkVersion=2.8.0
|
||||
buildNumber=120
|
||||
appVersion=19.0.3
|
||||
sdkVersion=1.21.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#Fri Mar 08 13:36:51 CET 2019
|
||||
#Wed Dec 19 12:02:47 CET 2018
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-all.zip
|
||||
|
||||
17
android/scripts/publish-android-jsc.sh
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
CWD=$(dirname $0)
|
||||
MVN_REPO=$(realpath $1)
|
||||
JSC_VERSION="r"$(jq -r '.dependencies."jsc-android"' ${CWD}/../../package.json | cut -d . -f 1)
|
||||
|
||||
pushd ${CWD}/../../node_modules/jsc-android/dist/org/webkit/android-jsc/${JSC_VERSION}
|
||||
|
||||
mvn \
|
||||
deploy:deploy-file \
|
||||
-Durl=file://${MVN_REPO} \
|
||||
-Dfile=android-jsc-${JSC_VERSION}.aar \
|
||||
-Dpackaging=aar \
|
||||
-DgeneratePom=false \
|
||||
-DpomFile=android-jsc-${JSC_VERSION}.pom
|
||||
|
||||
popd
|
||||
19
android/scripts/publish-react-native.sh
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
|
||||
CWD=$(dirname $0)
|
||||
MVN_REPO=$(realpath $1)
|
||||
RN_VERSION=$(jq -r '.dependencies."react-native"' ${CWD}/../../package.json)
|
||||
|
||||
pushd ${CWD}/../../node_modules/react-native/android/com/facebook/react/react-native/${RN_VERSION}
|
||||
|
||||
mvn \
|
||||
deploy:deploy-file \
|
||||
-Durl=file://${MVN_REPO} \
|
||||
-Dfile=react-native-${RN_VERSION}.aar \
|
||||
-Dpackaging=aar \
|
||||
-Dsources=react-native-${RN_VERSION}-sources.jar \
|
||||
-Djavadoc=react-native-${RN_VERSION}-javadoc.jar \
|
||||
-DgeneratePom=false \
|
||||
-DpomFile=react-native-${RN_VERSION}.pom
|
||||
|
||||
popd
|
||||
@@ -1,111 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e -u
|
||||
|
||||
|
||||
THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd)
|
||||
DEFAULT_MVN_REPO="${THIS_DIR}/../../../jitsi-maven-repository/releases"
|
||||
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-)
|
||||
DO_GIT_TAG=${GIT_TAG:-0}
|
||||
|
||||
if [[ $THE_MVN_REPO == http* ]]; then
|
||||
MVN_HTTP=1
|
||||
else
|
||||
MVN_REPO_PATH=$(realpath $THE_MVN_REPO)
|
||||
THE_MVN_REPO="file:${MVN_REPO_PATH}"
|
||||
fi
|
||||
|
||||
export MVN_REPO=$THE_MVN_REPO
|
||||
|
||||
echo "Releasing Jitsi Meet SDK ${SDK_VERSION}"
|
||||
echo "Using ${MVN_REPO} as the Maven repo"
|
||||
|
||||
if [[ $MVN_HTTP == 1 ]]; then
|
||||
# Push React Native
|
||||
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}
|
||||
mvn \
|
||||
deploy:deploy-file \
|
||||
-Durl=${MVN_REPO} \
|
||||
-DrepositoryId=${MVN_REPO_ID} \
|
||||
-Dfile=react-native-${RN_VERSION}.aar \
|
||||
-Dpackaging=aar \
|
||||
-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
|
||||
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}
|
||||
mvn \
|
||||
deploy:deploy-file \
|
||||
-Durl=${MVN_REPO} \
|
||||
-Dfile=react-native-${RN_VERSION}.aar \
|
||||
-Dpackaging=aar \
|
||||
-DgeneratePom=false \
|
||||
-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
|
||||
echo "Building and publishing the Jitsi Meet SDK"
|
||||
pushd ${THIS_DIR}/../
|
||||
./gradlew clean assembleRelease publish
|
||||
popd
|
||||
|
||||
if [[ $DO_GIT_TAG == 1 ]]; then
|
||||
# The artifacts are now on the Maven repo, commit them
|
||||
pushd ${MVN_REPO_PATH}
|
||||
git add -A .
|
||||
git commit -m "Jitsi Meet SDK + dependencies: ${SDK_VERSION}"
|
||||
popd
|
||||
|
||||
# Tag the release
|
||||
git tag android-sdk-${SDK_VERSION}
|
||||
fi
|
||||
|
||||
# Done!
|
||||
echo "Finished! Don't forget to push the tag and the Maven repo artifacts."
|
||||
@@ -1,26 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This script is executed bt Gradle to start the React packager for Debug
|
||||
# targets.
|
||||
|
||||
THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd)
|
||||
|
||||
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"
|
||||
exit 2
|
||||
fi
|
||||
else
|
||||
CMD="${THIS_DIR}/../../node_modules/react-native/scripts/launchPackager.command"
|
||||
if [[ `uname` == "Darwin" ]]; then
|
||||
open -g "${CMD}" || echo "Can't start packager automatically"
|
||||
else
|
||||
xdg-open "${CMD}" || echo "Can't start packager automatically"
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -1,2 +0,0 @@
|
||||
connection.project.dir=..
|
||||
eclipse.preferences.version=1
|
||||
@@ -1,5 +1,3 @@
|
||||
import groovy.json.JsonSlurper
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
@@ -12,71 +10,39 @@ android {
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
buildConfigField "boolean", "LIBRE_BUILD", "${rootProject.ext.libreBuild}"
|
||||
}
|
||||
debug {}
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
buildConfigField "boolean", "LIBRE_BUILD", "${rootProject.ext.libreBuild}"
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
if (rootProject.ext.libreBuild) {
|
||||
srcDir "src"
|
||||
exclude "**/AmplitudeModule.java"
|
||||
}
|
||||
exclude "test/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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'
|
||||
|
||||
//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.android.support:support-v4:${rootProject.ext.supportLibVersion}"
|
||||
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
|
||||
|
||||
implementation 'org.webkit:android-jsc:+'
|
||||
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'
|
||||
}
|
||||
}
|
||||
api 'com.facebook.react:react-native:+'
|
||||
|
||||
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-google-signin")) {
|
||||
exclude group: 'com.google.android.gms'
|
||||
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')
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
}
|
||||
@@ -141,16 +107,19 @@ android.libraryVariants.all { def variant ->
|
||||
|
||||
currentBundleTask.ext.generatedResFolders = files(resourcesDir).builtBy(currentBundleTask)
|
||||
currentBundleTask.ext.generatedAssetsFolders = files(jsBundleDir).builtBy(currentBundleTask)
|
||||
|
||||
variant.registerGeneratedResFolders(currentBundleTask.generatedResFolders)
|
||||
variant.mergeResources.dependsOn(currentBundleTask)
|
||||
|
||||
def mergeAssetsTask = variant.mergeAssetsProvider.get()
|
||||
def mergeResourcesTask = variant.mergeResourcesProvider.get()
|
||||
def assetsDir = variant.mergeAssets.outputDir
|
||||
|
||||
mergeAssetsTask.dependsOn(currentBundleTask)
|
||||
mergeResourcesTask.dependsOn(currentBundleTask)
|
||||
|
||||
mergeAssetsTask.doLast {
|
||||
def assetsDir = mergeAssetsTask.outputDir
|
||||
variant.mergeAssets.doLast {
|
||||
// Bundle fonts
|
||||
//
|
||||
copy {
|
||||
from("${projectDir}/../../fonts/jitsi.ttf")
|
||||
into("${assetsDir}/fonts")
|
||||
}
|
||||
|
||||
// Bundle sounds
|
||||
//
|
||||
@@ -158,8 +127,6 @@ android.libraryVariants.all { def variant ->
|
||||
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")
|
||||
@@ -173,18 +140,18 @@ android.libraryVariants.all { def variant ->
|
||||
if (currentBundleTask.enabled) {
|
||||
copy {
|
||||
from(jsBundleFile)
|
||||
into(assetsDir)
|
||||
into("${assetsDir}/")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mergeResourcesTask.doLast {
|
||||
variant.mergeResources.doLast {
|
||||
// Copy React resources
|
||||
//
|
||||
if (currentBundleTask.enabled) {
|
||||
copy {
|
||||
from(resourcesDir)
|
||||
into(mergeResourcesTask.outputDir)
|
||||
into(variant.mergeResources.outputDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,7 +163,7 @@ publishing {
|
||||
aarArchive(MavenPublication) {
|
||||
groupId 'org.jitsi.react'
|
||||
artifactId 'jitsi-meet-sdk'
|
||||
version System.env.OVERRIDE_SDK_VERSION ?: project.sdkVersion
|
||||
version project.sdkVersion
|
||||
|
||||
artifact("${project.buildDir}/outputs/aar/${project.name}-release.aar") {
|
||||
extension "aar"
|
||||
@@ -215,7 +182,8 @@ publishing {
|
||||
def groupId = it.moduleGroup
|
||||
def artifactId = it.moduleName
|
||||
|
||||
if (artifactId.startsWith('react-native-') && groupId.equals('jitsi-meet')) {
|
||||
if (artifactId.startsWith('react-native-')
|
||||
&& groupId.equals('jitsi-meet')) {
|
||||
groupId = rootProject.ext.moduleGroupId
|
||||
}
|
||||
|
||||
@@ -224,27 +192,11 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
repositories {
|
||||
maven {
|
||||
url rootProject.ext.mavenRepo
|
||||
if (!rootProject.ext.mavenRepo.startsWith("file")) {
|
||||
credentials {
|
||||
username rootProject.ext.mavenUser
|
||||
password rootProject.ext.mavenPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
maven { url "file:${rootProject.projectDir}/../../jitsi-maven-repository/releases" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,52 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.jitsi.meet.sdk">
|
||||
<!-- XXX ACCESS_NETWORK_STATE is required by WebRTC. -->
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<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"/>
|
||||
<!-- XXX ACCESS_NETWORK_STATE is required by WebRTC. -->
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS"/>
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<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-feature
|
||||
android:glEsVersion="0x00020000"
|
||||
android:required="true" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera.autofocus"
|
||||
android:required="false" />
|
||||
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera.autofocus"
|
||||
android:required="false" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true">
|
||||
<activity
|
||||
android:name=".JitsiMeetActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
|
||||
android:launchMode="singleTask"
|
||||
android:resizeableActivity="true"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||
|
||||
<service
|
||||
android:name=".ConnectionService"
|
||||
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.telecom.ConnectionService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service android:name="org.jitsi.meet.sdk.JitsiMeetOngoingConferenceService" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true">
|
||||
<activity
|
||||
android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||
<service android:name="org.jitsi.meet.sdk.ConnectionService"
|
||||
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.telecom.ConnectionService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -1,118 +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.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
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;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Implements the react-native module for the Amplitude integration.
|
||||
*/
|
||||
@ReactModule(name = AmplitudeModule.NAME)
|
||||
class AmplitudeModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
public static final String NAME = "Amplitude";
|
||||
public static final String JITSI_PREFERENCES = "jitsi-preferences";
|
||||
public static final String AMPLITUDE_DEVICE_ID_KEY = "amplitudeDeviceId";
|
||||
|
||||
public AmplitudeModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the Amplitude SDK.
|
||||
*
|
||||
* @param instanceName The name of the Amplitude instance. Should
|
||||
* be used only for multi-project logging.
|
||||
* @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.
|
||||
SharedPreferences sharedPreferences = getReactApplicationContext().getSharedPreferences(JITSI_PREFERENCES, Context.MODE_PRIVATE);
|
||||
String android_id = sharedPreferences.getString(AMPLITUDE_DEVICE_ID_KEY, "");
|
||||
if (!TextUtils.isEmpty(android_id)) {
|
||||
Amplitude.getInstance(instanceName).setDeviceId(android_id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user ID for an Amplitude instance.
|
||||
*
|
||||
* @param instanceName The name of the Amplitude instance.
|
||||
* @param userId The new value for the user ID.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void setUserId(String instanceName, String userId) {
|
||||
Amplitude.getInstance(instanceName).setUserId(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user properties for an Amplitude instance.
|
||||
*
|
||||
* @param instanceName The name of the Amplitude instance.
|
||||
* @param userProps JSON string with user properties to be set.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void setUserProperties(String instanceName, ReadableMap userProps) {
|
||||
if (userProps != null) {
|
||||
Amplitude.getInstance(instanceName).setUserProperties(
|
||||
new JSONObject(userProps.toHashMap()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an analytics event.
|
||||
*
|
||||
* @param instanceName The name of the Amplitude instance.
|
||||
* @param eventType The event type.
|
||||
* @param eventPropsString JSON string with the event properties.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void logEvent(String instanceName, String eventType, String eventPropsString) {
|
||||
try {
|
||||
JSONObject eventProps = new JSONObject(eventPropsString);
|
||||
Amplitude.getInstance(instanceName).logEvent(eventType, eventProps);
|
||||
} catch (JSONException e) {
|
||||
JitsiMeetLogger.e(e, "Error logging event");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
}
|
||||
@@ -15,21 +15,17 @@ 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;
|
||||
|
||||
@ReactModule(name = AndroidSettingsModule.NAME)
|
||||
class AndroidSettingsModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
public static final String NAME = "AndroidSettings";
|
||||
|
||||
public AndroidSettingsModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
return "AndroidSettings";
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
|
||||
@@ -23,17 +23,13 @@ import android.content.pm.PackageManager;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@ReactModule(name = AppInfoModule.NAME)
|
||||
class AppInfoModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
public static final String NAME = "AppInfo";
|
||||
|
||||
public AppInfoModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
@@ -64,9 +60,6 @@ class AppInfoModule
|
||||
|
||||
Map<String, Object> constants = new HashMap<>();
|
||||
|
||||
constants.put(
|
||||
"buildNumber",
|
||||
packageInfo == null ? "" : String.valueOf(packageInfo.versionCode));
|
||||
constants.put(
|
||||
"name",
|
||||
applicationInfo == null
|
||||
@@ -75,13 +68,12 @@ class AppInfoModule
|
||||
constants.put(
|
||||
"version",
|
||||
packageInfo == null ? "" : packageInfo.versionName);
|
||||
constants.put("LIBRE_BUILD", BuildConfig.LIBRE_BUILD);
|
||||
|
||||
return constants;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
return "AppInfo";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -27,9 +35,6 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
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;
|
||||
@@ -53,9 +58,8 @@ import java.util.concurrent.Executors;
|
||||
* Before a call has started and after it has ended the
|
||||
* {@code AudioModeModule.DEFAULT} mode should be used.
|
||||
*/
|
||||
@ReactModule(name = AudioModeModule.NAME)
|
||||
class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
public static final String NAME = "AudioMode";
|
||||
class AudioModeModule extends ReactContextBaseJavaModule
|
||||
implements AudioManager.OnAudioFocusChangeListener {
|
||||
|
||||
/**
|
||||
* Constants representing the audio mode.
|
||||
@@ -66,39 +70,180 @@ 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 name of {@code AudioModeModule} to be used in the React Native
|
||||
* bridge.
|
||||
*/
|
||||
private static final String MODULE_NAME = "AudioMode";
|
||||
|
||||
/**
|
||||
* The {@code Log} tag {@code AudioModeModule} is to log messages with.
|
||||
*/
|
||||
static final String TAG = NAME;
|
||||
static final String TAG = MODULE_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 +253,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 +268,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 +293,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 +329,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 +337,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);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -197,45 +373,133 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
*/
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
return MODULE_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (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 +514,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 +528,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 +582,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
return;
|
||||
}
|
||||
|
||||
runInAudioThread(new Runnable() {
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
boolean success;
|
||||
@@ -287,33 +591,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 +675,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 +715,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 +734,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
@@ -179,7 +170,7 @@ public abstract class BaseReactView<ListenerT>
|
||||
* @param data - The details of the event associated with/specific to the
|
||||
* specified {@code name}.
|
||||
*/
|
||||
protected abstract void onExternalAPIEvent(String name, ReadableMap data);
|
||||
public abstract void onExternalAPIEvent(String name, ReadableMap data);
|
||||
|
||||
protected void onExternalAPIEvent(
|
||||
Map<String, Method> listenerMethods,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
@@ -92,13 +81,6 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
return new ArrayList<>(connections.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} if running a Samsung device.
|
||||
*/
|
||||
static boolean isSamsungDevice() {
|
||||
return android.os.Build.MANUFACTURER.toLowerCase().contains("samsung");
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a start call promise.
|
||||
*
|
||||
@@ -123,17 +105,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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,14 +129,6 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
ConnectionImpl connection = connections.get(callUUID);
|
||||
|
||||
if (connection != null) {
|
||||
if (isSamsungDevice()) {
|
||||
// Required to release the audio focus correctly.
|
||||
connection.setOnHold();
|
||||
// Prevents from including in the native phone calls history
|
||||
connection.setConnectionProperties(
|
||||
Connection.PROPERTY_SELF_MANAGED
|
||||
| Connection.PROPERTY_IS_EXTERNAL_CALL);
|
||||
}
|
||||
// Note that the connection is not removed from the list here, but
|
||||
// in ConnectionImpl's state changed callback. It's a safer
|
||||
// approach, because in case the app would crash on the JavaScript
|
||||
@@ -164,7 +137,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 +169,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 +213,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 +243,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 +253,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 +270,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,10 +332,11 @@ 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(
|
||||
ReactContextUtils.emitEvent(
|
||||
null,
|
||||
"org.jitsi.meet:features/connection_service#disconnect",
|
||||
data);
|
||||
// The JavaScript side will not go back to the native with
|
||||
@@ -374,10 +353,11 @@ 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(
|
||||
ReactContextUtils.emitEvent(
|
||||
null,
|
||||
"org.jitsi.meet:features/connection_service#abort",
|
||||
data);
|
||||
// The JavaScript side will not go back to the native with
|
||||
@@ -392,7 +372,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 +387,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 +403,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);
|
||||
|
||||
@@ -22,7 +22,7 @@ import android.app.Activity;
|
||||
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
|
||||
|
||||
/**
|
||||
* Defines the default behavior of {@code JitsiMeetFragment} and
|
||||
* Defines the default behavior of {@code JitsiMeetActivity} and
|
||||
* {@code JitsiMeetView} upon invoking the back button if no
|
||||
* {@code JitsiMeetView} handles the invocation. For example, a
|
||||
* {@code JitsiMeetView} may (1) handle the invocation of the back button
|
||||
|
||||
@@ -16,24 +16,20 @@
|
||||
|
||||
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.
|
||||
*/
|
||||
@ReactModule(name = ExternalAPIModule.NAME)
|
||||
class ExternalAPIModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
public static final String NAME = "ExternalAPI";
|
||||
|
||||
private static final String TAG = NAME;
|
||||
private static final String TAG = ExternalAPIModule.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* Initializes a new module instance. There shall be a single instance of
|
||||
@@ -53,7 +49,7 @@ class ExternalAPIModule
|
||||
*/
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
return "ExternalAPI";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,20 +63,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,75 +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 android.os.Bundle;
|
||||
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
|
||||
public class JitsiMeet {
|
||||
/**
|
||||
* Default {@link JitsiMeetConferenceOptions} which will be used for all conferences. When
|
||||
* joining a conference these options will be merged with the ones passed to
|
||||
* {@link JitsiMeetView} join().
|
||||
*/
|
||||
private static JitsiMeetConferenceOptions defaultConferenceOptions;
|
||||
|
||||
public static JitsiMeetConferenceOptions getDefaultConferenceOptions() {
|
||||
return defaultConferenceOptions;
|
||||
}
|
||||
|
||||
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}.
|
||||
*
|
||||
* @return a {@link Bundle} with the default conference options.
|
||||
*/
|
||||
static Bundle getDefaultProps() {
|
||||
if (defaultConferenceOptions != null) {
|
||||
return defaultConferenceOptions.asProps();
|
||||
}
|
||||
|
||||
return new Bundle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in development mode. It displays the React Native development menu.
|
||||
*/
|
||||
public static void showDevOptions() {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.showDevOptionsDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* 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,212 +17,309 @@
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import android.provider.Settings;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.modules.core.PermissionListener;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* A base activity for SDK users to embed. It uses {@link JitsiMeetFragment} to do the heavy
|
||||
* lifting and wires the remaining Activity lifecycle methods so it works out of the box.
|
||||
* Base Activity for applications integrating Jitsi Meet at a higher level. It
|
||||
* contains all the required wiring between the {@code JitsiMeetView} and
|
||||
* the Activity lifecycle methods already implemented.
|
||||
*
|
||||
* In this activity we use a single {@code JitsiMeetView} instance. This
|
||||
* instance gives us access to a view which displays the welcome page and the
|
||||
* conference itself. All lifetime methods associated with this Activity are
|
||||
* hooked to the React Native subsystem via proxy calls through the
|
||||
* {@code JitsiMeetView} static methods.
|
||||
*/
|
||||
public class JitsiMeetActivity extends FragmentActivity
|
||||
implements JitsiMeetActivityInterface, JitsiMeetViewListener {
|
||||
public class JitsiMeetActivity
|
||||
extends AppCompatActivity implements JitsiMeetActivityInterface {
|
||||
|
||||
protected static final String TAG = JitsiMeetActivity.class.getSimpleName();
|
||||
/**
|
||||
* The request code identifying requests for the permission to draw on top
|
||||
* of other apps. The value must be 16-bit and is arbitrarily chosen here.
|
||||
*/
|
||||
private static final int OVERLAY_PERMISSION_REQUEST_CODE
|
||||
= (int) (Math.random() * Short.MAX_VALUE);
|
||||
|
||||
private static final String ACTION_JITSI_MEET_CONFERENCE = "org.jitsi.meet.CONFERENCE";
|
||||
private static final String JITSI_MEET_CONFERENCE_OPTIONS = "JitsiMeetConferenceOptions";
|
||||
/**
|
||||
* The default base {@code URL} used to join a conference when a partial URL
|
||||
* (e.g. a room name only) is specified. The value is used only while
|
||||
* {@link #view} equals {@code null}.
|
||||
*/
|
||||
private URL defaultURL;
|
||||
|
||||
// Helpers for starting the activity
|
||||
//
|
||||
/**
|
||||
* Instance of the {@link JitsiMeetView} which this activity will display.
|
||||
*/
|
||||
private JitsiMeetView view;
|
||||
|
||||
public static void launch(Context context, JitsiMeetConferenceOptions options) {
|
||||
Intent intent = new Intent(context, JitsiMeetActivity.class);
|
||||
intent.setAction(ACTION_JITSI_MEET_CONFERENCE);
|
||||
intent.putExtra(JITSI_MEET_CONFERENCE_OPTIONS, options);
|
||||
context.startActivity(intent);
|
||||
/**
|
||||
* Whether Picture-in-Picture is enabled. The value is used only while
|
||||
* {@link #view} equals {@code null}.
|
||||
*/
|
||||
private Boolean pictureInPictureEnabled;
|
||||
|
||||
/**
|
||||
* Whether the Welcome page is enabled. The value is used only while
|
||||
* {@link #view} equals {@code null}.
|
||||
*/
|
||||
private boolean welcomePageEnabled;
|
||||
|
||||
private boolean canRequestOverlayPermission() {
|
||||
return
|
||||
BuildConfig.DEBUG
|
||||
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
&& getApplicationInfo().targetSdkVersion
|
||||
>= Build.VERSION_CODES.M;
|
||||
}
|
||||
|
||||
public static void launch(Context context, String url) {
|
||||
JitsiMeetConferenceOptions options
|
||||
= new JitsiMeetConferenceOptions.Builder().setRoom(url).build();
|
||||
launch(context, options);
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#getDefaultURL()
|
||||
*/
|
||||
public URL getDefaultURL() {
|
||||
return view == null ? defaultURL : view.getDefaultURL();
|
||||
}
|
||||
|
||||
// Overrides
|
||||
//
|
||||
/**
|
||||
* Initializes the {@link #view} of this {@code JitsiMeetActivity} with a
|
||||
* new {@link JitsiMeetView} instance.
|
||||
*/
|
||||
private void initializeContentView() {
|
||||
JitsiMeetView view = initializeView();
|
||||
|
||||
if (view != null) {
|
||||
// XXX Allow extenders who override initializeView() to configure
|
||||
// the view before the first loadURL(). Probably works around a
|
||||
// problem related to ReactRootView#setAppProperties().
|
||||
view.loadURL(null);
|
||||
|
||||
this.view = view;
|
||||
setContentView(this.view);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new {@link JitsiMeetView} instance.
|
||||
*
|
||||
* @return a new {@code JitsiMeetView} instance.
|
||||
*/
|
||||
protected JitsiMeetView initializeView() {
|
||||
JitsiMeetView view = new JitsiMeetView(this);
|
||||
|
||||
// XXX Before calling JitsiMeetView#loadURL, make sure to call whatever
|
||||
// is documented to need such an order in order to take effect:
|
||||
view.setDefaultURL(defaultURL);
|
||||
if (pictureInPictureEnabled != null) {
|
||||
view.setPictureInPictureEnabled(
|
||||
pictureInPictureEnabled.booleanValue());
|
||||
}
|
||||
view.setWelcomePageEnabled(welcomePageEnabled);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#isPictureInPictureEnabled()
|
||||
*/
|
||||
public boolean isPictureInPictureEnabled() {
|
||||
return
|
||||
view == null
|
||||
? pictureInPictureEnabled
|
||||
: view.isPictureInPictureEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#isWelcomePageEnabled()
|
||||
*/
|
||||
public boolean isWelcomePageEnabled() {
|
||||
return view == null ? welcomePageEnabled : view.isWelcomePageEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given URL and displays the conference. If the specified URL is
|
||||
* null, the welcome page is displayed instead.
|
||||
*
|
||||
* @param url The conference URL.
|
||||
*/
|
||||
public void loadURL(@Nullable URL url) {
|
||||
view.loadURL(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(
|
||||
int requestCode,
|
||||
int resultCode,
|
||||
Intent data) {
|
||||
if (requestCode == OVERLAY_PERMISSION_REQUEST_CODE
|
||||
&& canRequestOverlayPermission()) {
|
||||
if (Settings.canDrawOverlays(this)) {
|
||||
initializeContentView();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ReactActivityLifecycleCallbacks.onActivityResult(
|
||||
this, requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
ReactActivityLifecycleCallbacks.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_jitsi_meet);
|
||||
// In Debug builds React needs permission to write over other apps in
|
||||
// order to display the warning and error overlays.
|
||||
if (canRequestOverlayPermission() && !Settings.canDrawOverlays(this)) {
|
||||
Intent intent
|
||||
= new Intent(
|
||||
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
|
||||
Uri.parse("package:" + getPackageName()));
|
||||
|
||||
if (!extraInitialize()) {
|
||||
initialize();
|
||||
startActivityForResult(intent, OVERLAY_PERMISSION_REQUEST_CODE);
|
||||
return;
|
||||
}
|
||||
|
||||
initializeContentView();
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
leave();
|
||||
|
||||
super.finish();
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
//
|
||||
|
||||
protected JitsiMeetView getJitsiView() {
|
||||
JitsiMeetFragment fragment
|
||||
= (JitsiMeetFragment) getSupportFragmentManager().findFragmentById(R.id.jitsiFragment);
|
||||
return fragment.getJitsiView();
|
||||
}
|
||||
|
||||
public void join(@Nullable String url) {
|
||||
JitsiMeetConferenceOptions options
|
||||
= new JitsiMeetConferenceOptions.Builder()
|
||||
.setRoom(url)
|
||||
.build();
|
||||
join(options);
|
||||
}
|
||||
|
||||
public void join(JitsiMeetConferenceOptions options) {
|
||||
getJitsiView().join(options);
|
||||
}
|
||||
|
||||
public void leave() {
|
||||
getJitsiView().leave();
|
||||
}
|
||||
|
||||
private @Nullable JitsiMeetConferenceOptions getConferenceOptions(Intent intent) {
|
||||
String action = intent.getAction();
|
||||
|
||||
if (Intent.ACTION_VIEW.equals(action)) {
|
||||
Uri uri = intent.getData();
|
||||
if (uri != null) {
|
||||
return new JitsiMeetConferenceOptions.Builder().setRoom(uri.toString()).build();
|
||||
}
|
||||
} else if (ACTION_JITSI_MEET_CONFERENCE.equals(action)) {
|
||||
return intent.getParcelableExtra(JITSI_MEET_CONFERENCE_OPTIONS);
|
||||
if (view != null) {
|
||||
view.dispose();
|
||||
view = null;
|
||||
}
|
||||
|
||||
return null;
|
||||
ReactActivityLifecycleCallbacks.onHostDestroy(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function called during activity initialization. If {@code true} is returned, the
|
||||
* initialization is delayed and the {@link JitsiMeetActivity#initialize()} method is not
|
||||
* called. In this case, it's up to the subclass to call the initialize method when ready.
|
||||
*
|
||||
* This is mainly required so we do some extra initialization in the Jitsi Meet app.
|
||||
*
|
||||
* @return {@code true} if the initialization will be delayed, {@code false} otherwise.
|
||||
*/
|
||||
protected boolean extraInitialize() {
|
||||
// ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
ReactInstanceManager reactInstanceManager;
|
||||
|
||||
if (!super.onKeyUp(keyCode, event)
|
||||
&& BuildConfig.DEBUG
|
||||
&& (reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager())
|
||||
!= null
|
||||
&& keyCode == KeyEvent.KEYCODE_MENU) {
|
||||
reactInstanceManager.showDevOptionsDialog();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void initialize() {
|
||||
// Listen for conference events.
|
||||
getJitsiView().setListener(this);
|
||||
|
||||
// Join the room specified by the URL the app was launched with.
|
||||
// Joining without the room option displays the welcome page.
|
||||
join(getConferenceOptions(getIntent()));
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
// XXX At least twice we received bug reports about malfunctioning
|
||||
// loadURL in the Jitsi Meet SDK while the Jitsi Meet app seemed to
|
||||
// functioning as expected in our testing. But that was to be expected
|
||||
// because the app does not exercise loadURL. In order to increase the
|
||||
// test coverage of loadURL, channel deep linking through loadURL.
|
||||
Uri uri;
|
||||
|
||||
JitsiMeetConferenceOptions options;
|
||||
|
||||
if ((options = getConferenceOptions(intent)) != null) {
|
||||
join(options);
|
||||
if (Intent.ACTION_VIEW.equals(intent.getAction())
|
||||
&& (uri = intent.getData()) != null
|
||||
&& JitsiMeetView.loadURLStringInViews(uri.toString())) {
|
||||
return;
|
||||
}
|
||||
|
||||
JitsiMeetActivityDelegate.onNewIntent(intent);
|
||||
ReactActivityLifecycleCallbacks.onNewIntent(intent);
|
||||
}
|
||||
|
||||
// https://developer.android.com/reference/android/support/v4/app/ActivityCompat.OnRequestPermissionsResultCallback
|
||||
@Override
|
||||
public void onRequestPermissionsResult(
|
||||
final int requestCode,
|
||||
final String[] permissions,
|
||||
final int[] grantResults) {
|
||||
ReactActivityLifecycleCallbacks.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
ReactActivityLifecycleCallbacks.onHostResume(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
|
||||
ReactActivityLifecycleCallbacks.onHostPause(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUserLeaveHint() {
|
||||
getJitsiView().enterPictureInPicture();
|
||||
if (view != null) {
|
||||
view.enterPictureInPicture();
|
||||
}
|
||||
}
|
||||
|
||||
// JitsiMeetActivityInterface
|
||||
//
|
||||
|
||||
/**
|
||||
* Implementation of the {@code PermissionAwareActivity} interface.
|
||||
*/
|
||||
@Override
|
||||
public void requestPermissions(String[] permissions, int requestCode, PermissionListener listener) {
|
||||
JitsiMeetActivityDelegate.requestPermissions(this, permissions, requestCode, listener);
|
||||
ReactActivityLifecycleCallbacks.requestPermissions(this, permissions, requestCode, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
JitsiMeetActivityDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#setDefaultURL(URL)
|
||||
*/
|
||||
public void setDefaultURL(URL defaultURL) {
|
||||
if (view == null) {
|
||||
this.defaultURL = defaultURL;
|
||||
} else {
|
||||
view.setDefaultURL(defaultURL);
|
||||
}
|
||||
}
|
||||
|
||||
// JitsiMeetViewListener
|
||||
//
|
||||
|
||||
@Override
|
||||
public void onConferenceJoined(Map<String, Object> data) {
|
||||
JitsiMeetLogger.i("Conference joined: " + data);
|
||||
// Launch the service for the ongoing notification.
|
||||
JitsiMeetOngoingConferenceService.launch(this);
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#setPictureInPictureEnabled(boolean)
|
||||
*/
|
||||
public void setPictureInPictureEnabled(boolean pictureInPictureEnabled) {
|
||||
if (view == null) {
|
||||
this.pictureInPictureEnabled
|
||||
= Boolean.valueOf(pictureInPictureEnabled);
|
||||
} else {
|
||||
view.setPictureInPictureEnabled(pictureInPictureEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConferenceTerminated(Map<String, Object> data) {
|
||||
JitsiMeetLogger.i("Conference terminated: " + data);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConferenceWillJoin(Map<String, Object> data) {
|
||||
JitsiMeetLogger.i("Conference will join: " + data);
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#setWelcomePageEnabled(boolean)
|
||||
*/
|
||||
public void setWelcomePageEnabled(boolean welcomePageEnabled) {
|
||||
if (view == null) {
|
||||
this.welcomePageEnabled = welcomePageEnabled;
|
||||
} else {
|
||||
view.setWelcomePageEnabled(welcomePageEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
|
||||
import com.facebook.react.modules.core.PermissionAwareActivity;
|
||||
|
||||
/**
|
||||
* This interface serves as the umbrella interface that applications not using
|
||||
* {@code JitsiMeetFragment} must implement in order to ensure full
|
||||
* {@code JitsiMeetActivity} must implement in order to ensure full
|
||||
* functionality.
|
||||
*/
|
||||
public interface JitsiMeetActivityInterface
|
||||
|
||||
@@ -1,396 +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 android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
|
||||
/**
|
||||
* This class represents the options when joining a Jitsi Meet conference. The user can create an
|
||||
* instance by using {@link JitsiMeetConferenceOptions.Builder} and setting the desired options
|
||||
* there.
|
||||
*
|
||||
* The resulting {@link JitsiMeetConferenceOptions} object is immutable and represents how the
|
||||
* conference will be joined.
|
||||
*/
|
||||
public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
/**
|
||||
* Server where the conference should take place.
|
||||
*/
|
||||
private URL serverURL;
|
||||
/**
|
||||
* Room name.
|
||||
*/
|
||||
private String room;
|
||||
/**
|
||||
* Conference subject.
|
||||
*/
|
||||
private String subject;
|
||||
/**
|
||||
* JWT token used for authentication.
|
||||
*/
|
||||
private String token;
|
||||
|
||||
/**
|
||||
* Color scheme override, see: https://github.com/jitsi/jitsi-meet/blob/dbedee5e22e5dcf9c92db96ef5bb3c9982fc526d/react/features/base/color-scheme/defaultScheme.js
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
private Boolean audioMuted;
|
||||
private Boolean audioOnly;
|
||||
private Boolean videoMuted;
|
||||
|
||||
/**
|
||||
* USer information, to be used when no token is specified.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class used to build the immutable {@link JitsiMeetConferenceOptions} object.
|
||||
*/
|
||||
public static class Builder {
|
||||
private URL serverURL;
|
||||
private String room;
|
||||
private String subject;
|
||||
private String token;
|
||||
|
||||
private Bundle colorScheme;
|
||||
private Bundle featureFlags;
|
||||
|
||||
private Boolean audioMuted;
|
||||
private Boolean audioOnly;
|
||||
private Boolean videoMuted;
|
||||
|
||||
private JitsiMeetUserInfo userInfo;
|
||||
|
||||
public Builder() {
|
||||
featureFlags = new Bundle();
|
||||
}
|
||||
|
||||
/**\
|
||||
* Sets the server URL.
|
||||
* @param url - {@link URL} of the server where the conference should take place.
|
||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||
*/
|
||||
public Builder setServerURL(URL url) {
|
||||
this.serverURL = url;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the room where the conference will take place.
|
||||
* @param room - Name of the room.
|
||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||
*/
|
||||
public Builder setRoom(String room) {
|
||||
this.room = room;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the conference subject.
|
||||
* @param subject - Subject for the conference.
|
||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||
*/
|
||||
public Builder setSubject(String subject) {
|
||||
this.subject = subject;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the JWT token to be used for authentication when joining a conference.
|
||||
* @param token - The JWT token to be used for authentication.
|
||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||
*/
|
||||
public Builder setToken(String token) {
|
||||
this.token = token;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color scheme override so the app is themed. See:
|
||||
* https://github.com/jitsi/jitsi-meet/blob/master/react/features/base/color-scheme/defaultScheme.js
|
||||
* for the structure.
|
||||
* @param colorScheme - A color scheme to be applied to the app.
|
||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||
*/
|
||||
public Builder setColorScheme(Bundle colorScheme) {
|
||||
this.colorScheme = colorScheme;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the conference will be joined with the microphone muted.
|
||||
* @param muted - Muted indication.
|
||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||
*/
|
||||
public Builder setAudioMuted(boolean muted) {
|
||||
this.audioMuted = muted;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the conference will be joined in audio-only mode. In this mode no video is
|
||||
* sent or received.
|
||||
* @param audioOnly - Audio-mode indicator.
|
||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||
*/
|
||||
public Builder setAudioOnly(boolean audioOnly) {
|
||||
this.audioOnly = audioOnly;
|
||||
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Indicates the conference will be joined with the camera muted.
|
||||
* @param videoMuted - Muted indication.
|
||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||
*/
|
||||
public Builder setVideoMuted(boolean videoMuted) {
|
||||
this.videoMuted = videoMuted;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the welcome page enabled / disabled. The welcome page lists recent meetings and
|
||||
* calendar appointments and it's meant to be used by standalone applications. Defaults to
|
||||
* false.
|
||||
* @param enabled - Whether the welcome page should be enabled or not.
|
||||
* @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;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the immutable {@link JitsiMeetConferenceOptions} object with the configuration
|
||||
* that this {@link Builder} instance specified.
|
||||
* @return - The built {@link JitsiMeetConferenceOptions} object.
|
||||
*/
|
||||
public JitsiMeetConferenceOptions build() {
|
||||
JitsiMeetConferenceOptions options = new JitsiMeetConferenceOptions();
|
||||
|
||||
options.serverURL = this.serverURL;
|
||||
options.room = this.room;
|
||||
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;
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
private JitsiMeetConferenceOptions() {
|
||||
}
|
||||
|
||||
private JitsiMeetConferenceOptions(Parcel in) {
|
||||
serverURL = (URL) in.readSerializable();
|
||||
room = in.readString();
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
Bundle config = new Bundle();
|
||||
|
||||
if (audioMuted != null) {
|
||||
config.putBoolean("startWithAudioMuted", audioMuted);
|
||||
}
|
||||
if (audioOnly != null) {
|
||||
config.putBoolean("startAudioOnly", audioOnly);
|
||||
}
|
||||
if (videoMuted != null) {
|
||||
config.putBoolean("startWithVideoMuted", videoMuted);
|
||||
}
|
||||
if (subject != null) {
|
||||
config.putString("subject", subject);
|
||||
}
|
||||
|
||||
Bundle urlProps = new Bundle();
|
||||
|
||||
// The room is fully qualified
|
||||
if (room != null && room.contains("://")) {
|
||||
urlProps.putString("url", room);
|
||||
} else {
|
||||
if (serverURL != null) {
|
||||
urlProps.putString("serverURL", serverURL.toString());
|
||||
}
|
||||
if (room != null) {
|
||||
urlProps.putString("room", room);
|
||||
}
|
||||
}
|
||||
|
||||
if (token != null) {
|
||||
urlProps.putString("jwt", token);
|
||||
}
|
||||
|
||||
if (userInfo != null) {
|
||||
props.putBundle("userInfo", userInfo.asBundle());
|
||||
}
|
||||
|
||||
urlProps.putBundle("config", config);
|
||||
props.putBundle("url", urlProps);
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
// Parcelable interface
|
||||
//
|
||||
|
||||
public static final Creator<JitsiMeetConferenceOptions> CREATOR = new Creator<JitsiMeetConferenceOptions>() {
|
||||
@Override
|
||||
public JitsiMeetConferenceOptions createFromParcel(Parcel in) {
|
||||
return new JitsiMeetConferenceOptions(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JitsiMeetConferenceOptions[] newArray(int size) {
|
||||
return new JitsiMeetConferenceOptions[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeSerializable(serverURL);
|
||||
dest.writeString(room);
|
||||
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));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.Intent;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
/**
|
||||
* Base {@link Fragment} for applications integrating Jitsi Meet at a higher level. It
|
||||
* contains all the required wiring between the {@code JitsiMeetView} and
|
||||
* the Fragment lifecycle methods already implemented.
|
||||
*
|
||||
* In this fragment we use a single {@code JitsiMeetView} instance. This
|
||||
* instance gives us access to a view which displays the welcome page and the
|
||||
* conference itself. All lifecycle methods associated with this Fragment are
|
||||
* hooked to the React Native subsystem via proxy calls through the
|
||||
* {@code JitsiMeetActivityDelegate} static methods.
|
||||
*/
|
||||
public class JitsiMeetFragment extends Fragment {
|
||||
|
||||
/**
|
||||
* Instance of the {@link JitsiMeetView} which this activity will display.
|
||||
*/
|
||||
private JitsiMeetView view;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
return this.view = new JitsiMeetView(getActivity());
|
||||
}
|
||||
|
||||
public JitsiMeetView getJitsiView() {
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
JitsiMeetActivityDelegate.onActivityResult(
|
||||
getActivity(), requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
if (view != null) {
|
||||
view.dispose();
|
||||
view = null;
|
||||
}
|
||||
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
JitsiMeetActivityDelegate.onHostDestroy(getActivity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
JitsiMeetActivityDelegate.onHostResume(getActivity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
|
||||
JitsiMeetActivityDelegate.onHostPause(getActivity());
|
||||
}
|
||||
}
|
||||