Compare commits

..

8 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
3fd2f4052f android: workaround for building
Somehow the bundle doesn't get included in the APK when I build this branch on
one laptop, but works on another, go figure. All of this has changed with the RN
0.59 update, so this is just a band-aid for 19.0 builds.
2019-03-25 11:31:37 +01:00
Saúl Ibarra Corretgé
bf6c41adb1 android: update version 2019-03-25 11:12:46 +01:00
paweldomas
59f3dce01c fix(Android/ConnectionService): do not display the address
Turns out that on Samsung phones the calls placed with
the ConnectionService appear in the calls log as weird long numbers.
The system mangles the address we give it ("sip:meet.jit.si/something")
into this weird long number and the call to request.getAddress() returns
that. Turn off the presentation as neither this number nor our address
makes sense. This way the call appears as from "Unknown" caller in call
history which is still not perfect, but better than the random number.

Note that other phones will preserve the originally passed address value
(tested on One Plus 5).
2019-03-25 10:59:04 +01:00
paweldomas
6d1b891c4c fix(Android/ConnectionService): mic not working
Turns out the microphone will not work on some devices when starting in
"audio only", because the audio mode is not set to the MODE_IN_COMMUNICATION,
but to the MODE_IN_CALL. Calling setAudioModeIsVoip(true) makes
the system adjust to MODE_IN_COMMUNICATION and the mic works fine.
2019-03-25 10:58:56 +01:00
paweldomas
0062f8ae8e ref(AudioModeModule): check 1 method to enable ConnectionService 2019-03-25 10:58:46 +01:00
Saúl Ibarra Corretgé
eb63e19d63 android: updated version 2019-03-08 13:09:15 +01:00
Saúl Ibarra Corretgé
cd48f5cb20 android: updated version 2019-03-08 11:51:52 +01:00
Saúl Ibarra Corretgé
42e495e359 android: fix running on Android < M
The android.telecom.CallAudioState class was only added in API level 23 (Android
M), so make sure we don't import it in lower versioned devices.
2019-03-08 11:38:32 +01:00
1290 changed files with 32301 additions and 63814 deletions

View File

@@ -2,30 +2,22 @@
; We fork some components by platform
.*/*[.]android.js
; Ignore "BUCK" generated dirs
<PROJECT_ROOT>/\.buckd/
; Ignore unexpected extra "@providesModule"
.*/node_modules/.*/node_modules/fbjs/.*
node_modules/react-native/Libraries/react-native/React.js
; Ignore duplicate module providers
; For RN Apps installed via npm, "Libraries" folder is inside
; "node_modules/react-native" but in the source repo it is in the root
node_modules/react-native/Libraries/react-native/React.js
; Flow doesn't support platforms
.*/Libraries/Utilities/LoadingView.js
.*/Libraries/react-native/React.js
; Ignore polyfills
node_modules/react-native/Libraries/polyfills/.*
.*/Libraries/polyfills/.*
; These should not be required directly
; require from fbjs/lib instead: require('fbjs/lib/warning')
node_modules/warning/.*
; Flow doesn't support platforms
.*/Libraries/Utilities/HMRLoadingView.js
[untyped]
.*/node_modules/@react-native-community/cli/.*/.*
; Ignore metro
.*/node_modules/metro/.*
; Ignore packages in node_modules which we (i.e. the jitsi-meet project) have
; seen to cause errors and we have chosen not to fix.
@@ -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

View File

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

12
.gitignore vendored
View File

@@ -70,11 +70,8 @@ buck-out/
*/fastlane/Preview.html
*/fastlane/screenshots
# Build artifacts
# Bundle artifact
*.jsbundle
*.framework
android/app/debug
android/app/release
# precommit-hook
.jshintignore
@@ -83,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

View File

@@ -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

View File

@@ -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

View File

@@ -3,7 +3,6 @@ CLEANCSS = ./node_modules/.bin/cleancss
DEPLOY_DIR = libs
LIBJITSIMEET_DIR = node_modules/lib-jitsi-meet/
LIBFLAC_DIR = node_modules/libflacjs/dist/min/
RNNOISE_WASM_DIR = node_modules/rnnoise-wasm/dist/
NODE_SASS = ./node_modules/.bin/node-sass
NPM = npm
OUTPUT_DIR = .
@@ -21,7 +20,7 @@ compile:
clean:
rm -fr $(BUILD_DIR)
deploy: deploy-init deploy-appbundle deploy-rnnoise-binary deploy-lib-jitsi-meet deploy-libflac deploy-css deploy-local
deploy: deploy-init deploy-appbundle deploy-lib-jitsi-meet deploy-libflac deploy-css deploy-local
deploy-init:
rm -fr $(DEPLOY_DIR)
@@ -46,10 +45,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,7 +71,7 @@ deploy-css:
deploy-local:
([ ! -x deploy-local.sh ] || ./deploy-local.sh)
dev: deploy-init deploy-css deploy-rnnoise-binary deploy-lib-jitsi-meet deploy-libflac
dev: deploy-init deploy-css deploy-lib-jitsi-meet deploy-libflac
$(WEBPACK_DEV_SERVER)
source-package:

114
README.md
View File

@@ -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](#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 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).
@@ -31,9 +31,82 @@ You can get our mobile versions from here:
* [Android](https://play.google.com/store/apps/details?id=org.jitsi.meet)
* [iOS](https://itunes.apple.com/us/app/jitsi-meet/id1165103905)
## Development
## Building the sources
For web development see [here](doc/development.md), and for mobile see [here](doc/mobile.md).
Node.js >= 8 and npm >= 6 are required.
On Debian/Ubuntu systems, the required packages can be installed with:
```
sudo apt-get install npm nodejs
cd jitsi-meet
npm install
```
To build the Jitsi Meet application, just type
```
make
```
### 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
@@ -42,30 +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 does not (yet) provide a way of conducting multi-party conversations with end-to-end encryption.
Unless you consistently compare DTLS fingerprints with your peers vocally, the same goes for one-to-one calls.
As a result, your stream is encrypted on the network but decrypted on the machine that hosts the bridge when using Jitsi Meet.
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 Jitsi Meet architecture allows you to deploy your own version, including
all server components. In that case, your security guarantees will be roughly
equivalent to a direct one-to-one WebRTC call. This is the uniqueness of
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 [meet.jit.si](https://meet.jit.si) service is maintained by the Jitsi team
at [8x8](https://8x8.com).
## Security issues
We take security very seriously and develop all Jitsi projects to be secure and safe.
If you find (or simply suspect) a security issue in any of the Jitsi projects, please send us an email to security@jitsi.org.
**We encourage responsible disclosure for the sake of our users, so please reach out before posting in a public space.**
## Mobile app
Jitsi Meet is also available as a React Native app for Android and iOS.
Instructions on how to build it can be found [here](doc/mobile.md).
## Acknowledgements
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!

View File

@@ -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:

View File

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

View File

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

View File

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

View File

@@ -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,38 +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'
}
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 {
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"
}
}
}
}
@@ -68,24 +61,30 @@ repositories {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.appcompat:appcompat:1.0.2'
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 {
@@ -115,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) {

View File

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

View File

@@ -9,6 +9,13 @@
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# React Native
# Keep our interfaces so they can be used by other ProGuard rules.
@@ -53,9 +60,19 @@
-keep class sun.misc.Unsafe { *; }
-dontwarn java.nio.file.*
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
-keep class okio.** { *; }
-dontwarn okio.**
# FastImage + Glide
-keep public class com.dylanvann.fastimage.* {*;}
-keep public class com.dylanvann.fastimage.** {*;}
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
# WebRTC
-keep class org.webrtc.** { *; }
@@ -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,6 +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.** {*;}

View File

@@ -25,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>

View File

@@ -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());
}
});
}
}
}

View File

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

View File

@@ -17,134 +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"))
.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();
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 659 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 379 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 960 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

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

View File

@@ -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>

View File

@@ -12,7 +12,7 @@ buildscript {
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.2'
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'
@@ -25,10 +25,10 @@ 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" }
// Android JSC is installed from npm.
maven { url("$rootDir/../node_modules/jsc-android/dist") }
}
// Make sure we use the react-native version in node_modules and not the one
@@ -42,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]}"
}
}
}
}
@@ -56,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
@@ -106,6 +137,7 @@ allprojects {
extension "aar"
}
artifact(androidSourcesJar)
artifact(androidJavadocsJar)
pom.withXml {
def pomXml = asNode()
pomXml.appendNode('name', project.name)
@@ -156,13 +188,9 @@ 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 ?: ""
// Libre build
libreBuild = (System.env.LIBRE_BUILD ?: "false").toBoolean()
// Glide
excludeAppGlideModule = true
glideVersion = "4.7.1" // keep in sync with react-native-fast-image
}
// If Android SDK is not installed, accept its license so that it

View File

@@ -1,2 +0,0 @@
json_key_file("")
package_name("org.jitsi.meet")

View File

@@ -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: "Closed Beta",
json_key: ENV["JITSI_JSON_KEY_FILE"],
skip_upload_metadata: true,
skip_upload_images: true,
skip_upload_screenshots: true
)
end
end

View File

@@ -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).

View File

@@ -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=19.4.0
sdkVersion=2.4.0
buildNumber=120
appVersion=19.0.3
sdkVersion=1.21.0

View File

@@ -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

View 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

View 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

View File

@@ -1,107 +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 '.dependencies."react-native"' ${THIS_DIR}/../../package.json)
JSC_VERSION="r"$(jq -r '.dependencies."jsc-android"' ${THIS_DIR}/../../node_modules/react-native/package.json | cut -d . -f 1)
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 JSC
echo "Pushing JSC ${JSC_VERSION} to the Maven repo"
pushd ${THIS_DIR}/../../node_modules/jsc-android/dist/org/webkit/android-jsc/${JSC_VERSION}
mvn \
deploy:deploy-file \
-Durl=${MVN_REPO} \
-DrepositoryId=${MVN_REPO_ID} \
-Dfile=android-jsc-${JSC_VERSION}.aar \
-Dpackaging=aar \
-DgeneratePom=false \
-DpomFile=android-jsc-${JSC_VERSION}.pom || 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 JSC, if necessary
if [[ ! -d ${MVN_REPO}/org/webkit/android-jsc/${JSC_VERSION} ]]; then
echo "Pushing JSC ${JSC_VERSION} to the Maven repo"
pushd ${THIS_DIR}/../../node_modules/jsc-android/dist/org/webkit/android-jsc/${JSC_VERSION}
mvn \
deploy:deploy-file \
-Durl=${MVN_REPO} \
-Dfile=android-jsc-${JSC_VERSION}.aar \
-Dpackaging=aar \
-DgeneratePom=false \
-DpomFile=android-jsc-${JSC_VERSION}.pom
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."

View File

@@ -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

View File

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

View File

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

View File

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

View File

@@ -10,25 +10,10 @@ 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/"
}
}
}
}
@@ -36,36 +21,28 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.fragment:fragment:1.0.0'
implementation "com.android.support:support-v4:${rootProject.ext.supportLibVersion}"
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
api 'com.facebook.react:react-native:+'
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'
}
@@ -130,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
//
@@ -147,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")
@@ -162,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)
}
}
}
@@ -185,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"
@@ -204,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
}
@@ -218,14 +197,6 @@ publishing {
}
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" }
}
}

View File

@@ -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>

View File

@@ -1,102 +0,0 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.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 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
public void init(String instanceName, String apiKey) {
Amplitude.getInstance(instanceName).initialize(getCurrentActivity(), apiKey);
}
/**
* 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;
}
}

View File

@@ -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

View File

@@ -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";
}
}

View File

@@ -25,7 +25,8 @@ import android.content.pm.PackageManager;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.os.Build;
import androidx.annotation.RequiresApi;
import android.support.annotation.RequiresApi;
import android.util.Log;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
@@ -34,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;
@@ -60,12 +58,9 @@ 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
implements AudioManager.OnAudioFocusChangeListener {
public static final String NAME = "AudioMode";
/**
* Constants representing the audio mode.
* - DEFAULT: Used before and after every call. It represents the default
@@ -94,10 +89,16 @@ class AudioModeModule extends ReactContextBaseJavaModule
*/
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
@@ -122,7 +123,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
case DEVICE_SPEAKER:
return android.telecom.CallAudioState.ROUTE_SPEAKER;
default:
JitsiMeetLogger.e(TAG + " Unsupported device name: " + audioDevice);
Log.e(TAG, "Unsupported device name: " + audioDevice);
return android.telecom.CallAudioState.ROUTE_EARPIECE;
}
}
@@ -219,7 +220,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
}
availableDevices = devices;
JitsiMeetLogger.i(TAG + " Available audio devices: " +
Log.d(TAG, "Available audio devices: " +
availableDevices.toString());
// Reset user selection
@@ -231,6 +232,19 @@ class AudioModeModule extends ReactContextBaseJavaModule
}
};
/**
* {@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.
*/
@@ -244,11 +258,6 @@ class AudioModeModule extends ReactContextBaseJavaModule
private static final String DEVICE_HEADPHONES = "HEADPHONES";
private static final String DEVICE_SPEAKER = "SPEAKER";
/**
* Device change event.
*/
private static final String DEVICE_CHANGE_EVENT = "org.jitsi.meet:features/audio-mode#devices-update";
/**
* List of currently available audio devices.
*/
@@ -296,7 +305,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Do an initial detection on Android >= M.
onAudioDeviceChange();
runInAudioThread(onAudioDeviceChangeRunner);
} else {
// On Android < M, detect if we have an earpiece.
PackageManager pm = reactContext.getPackageManager();
@@ -320,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);
@@ -329,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);
}
});
}
@@ -360,7 +373,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
*/
@Override
public String getName() {
return NAME;
return MODULE_NAME;
}
/**
@@ -431,7 +444,8 @@ class AudioModeModule extends ReactContextBaseJavaModule
if (audioDevicesChanged) {
supportedRouteMask = newSupportedRoutes;
availableDevices = routesToDeviceNames(supportedRouteMask);
JitsiMeetLogger.i(TAG + " Available audio devices: "
Log.d(TAG,
"Available audio devices: "
+ availableDevices.toString());
}
@@ -443,10 +457,6 @@ class AudioModeModule extends ReactContextBaseJavaModule
// Reset user selection
userSelectedDevice = null;
// If the OS changes the Audio Route or Devices we could have lost
// the selected audio device
selectedDevice = null;
if (mode != -1) {
updateAudioRoute(mode);
}
@@ -465,7 +475,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN: {
JitsiMeetLogger.d(TAG + " Audio focus gained");
Log.d(TAG, "Audio focus gained");
// Some other application potentially stole our audio focus
// temporarily. Restore our mode.
if (audioFocusLost) {
@@ -477,7 +487,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
case AudioManager.AUDIOFOCUS_LOSS:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: {
JitsiMeetLogger.d(TAG + " Audio focus lost");
Log.d(TAG, "Audio focus lost");
audioFocusLost = true;
break;
}
@@ -504,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);
}
@@ -572,7 +582,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
return;
}
runInAudioThread(new Runnable() {
Runnable r = new Runnable() {
@Override
public void run() {
boolean success;
@@ -581,7 +591,10 @@ 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;
@@ -592,7 +605,8 @@ class AudioModeModule extends ReactContextBaseJavaModule
"Failed to set audio mode to " + mode);
}
}
});
};
runInAudioThread(r);
}
/**
@@ -617,14 +631,14 @@ class AudioModeModule extends ReactContextBaseJavaModule
@Override
public void onAudioDevicesAdded(
AudioDeviceInfo[] addedDevices) {
JitsiMeetLogger.d(TAG + " Audio devices added");
Log.d(TAG, "Audio devices added");
onAudioDeviceChange();
}
@Override
public void onAudioDevicesRemoved(
AudioDeviceInfo[] removedDevices) {
JitsiMeetLogger.d(TAG + " Audio devices removed");
Log.d(TAG, "Audio devices removed");
onAudioDeviceChange();
}
};
@@ -643,7 +657,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
BroadcastReceiver wiredHeadsetReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
JitsiMeetLogger.d(TAG + " Wired headset added / removed");
Log.d(TAG, "Wired headset added / removed");
onHeadsetDeviceChange();
}
};
@@ -661,7 +675,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
* {@code false}, otherwise.
*/
private boolean updateAudioRoute(int mode) {
JitsiMeetLogger.i(TAG + " Update audio route for mode: " + mode);
Log.d(TAG, "Update audio route for mode: " + mode);
if (mode == DEFAULT) {
if (!useConnectionService()) {
@@ -674,7 +688,6 @@ class AudioModeModule extends ReactContextBaseJavaModule
selectedDevice = null;
userSelectedDevice = null;
notifyDevicesChanged();
return true;
}
@@ -687,12 +700,13 @@ class AudioModeModule extends ReactContextBaseJavaModule
AudioManager.STREAM_VOICE_CALL,
AudioManager.AUDIOFOCUS_GAIN)
== AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
JitsiMeetLogger.w(TAG + " Audio focus 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.
@@ -701,6 +715,8 @@ 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;
}
@@ -718,7 +734,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
}
selectedDevice = audioDevice;
JitsiMeetLogger.i(TAG + " Selected audio device: " + audioDevice);
Log.d(TAG, "Selected audio device: " + audioDevice);
if (useConnectionService()) {
setAudioRoute(audioDevice);
@@ -726,7 +742,6 @@ class AudioModeModule extends ReactContextBaseJavaModule
setAudioRoutePreO(audioDevice);
}
notifyDevicesChanged();
return true;
}
}

View File

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

View File

@@ -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
@@ -78,7 +77,7 @@ class BluetoothHeadsetMonitor {
= (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
if (!audioManager.isBluetoothScoAvailableOffCall()) {
JitsiMeetLogger.w(AudioModeModule.TAG + " Bluetooth SCO is not available");
Log.w(AudioModeModule.TAG, "Bluetooth SCO is not available");
return;
}
@@ -94,7 +93,7 @@ class BluetoothHeadsetMonitor {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter == null) {
JitsiMeetLogger.w(AudioModeModule.TAG + " Device doesn't support Bluetooth");
Log.w(AudioModeModule.TAG, "Device doesn't support Bluetooth");
return false;
}
@@ -149,7 +148,9 @@ class BluetoothHeadsetMonitor {
switch (state) {
case BluetoothHeadset.STATE_CONNECTED:
case BluetoothHeadset.STATE_DISCONNECTED:
JitsiMeetLogger.d(AudioModeModule.TAG + " BT headset connection state changed: " + state);
Log.d(
AudioModeModule.TAG,
"BT headset connection state changed: " + state);
updateDevices();
break;
}
@@ -163,7 +164,9 @@ class BluetoothHeadsetMonitor {
switch (state) {
case AudioManager.SCO_AUDIO_STATE_CONNECTED:
case AudioManager.SCO_AUDIO_STATE_DISCONNECTED:
JitsiMeetLogger.d(AudioModeModule.TAG + " BT SCO connection state changed: " + state);
Log.d(
AudioModeModule.TAG,
"BT SCO connection state changed: " + state);
updateDevices();
break;
}

View File

@@ -5,6 +5,7 @@ import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.RequiresApi;
import android.telecom.CallAudioState;
import android.telecom.Connection;
import android.telecom.ConnectionRequest;
@@ -13,14 +14,12 @@ import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import androidx.annotation.RequiresApi;
import android.util.Log;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableNativeMap;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -64,16 +63,6 @@ public class ConnectionService extends android.telecom.ConnectionService {
static private final HashMap<String, Promise> startCallPromises
= new HashMap<>();
/**
* Aborts all ongoing connections. This is a last resort mechanism which forces all resources to
* be freed on the system in case of fatal error.
*/
static void abortConnections() {
for (ConnectionImpl connection: getConnections()) {
connection.onAbort();
}
}
/**
* Adds {@link ConnectionImpl} to the list.
*
@@ -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.
*
@@ -130,7 +112,9 @@ public class ConnectionService extends android.telecom.ConnectionService {
if (connection != null) {
connection.setActive();
} else {
JitsiMeetLogger.e("%s setConnectionActive - no connection for UUID: %s", TAG, callUUID);
Log.e(TAG, String.format(
"setConnectionActive - no connection for UUID: %s",
callUUID));
}
}
@@ -145,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
@@ -161,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);
}
}
@@ -193,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);
}
}
@@ -236,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;
@@ -264,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);
@@ -274,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);
@@ -289,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");
}
}
@@ -351,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
@@ -371,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
@@ -389,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();
}
@@ -402,7 +387,7 @@ public class ConnectionService extends android.telecom.ConnectionService {
*/
@Override
public void onCallAudioStateChanged(CallAudioState state) {
JitsiMeetLogger.d(TAG + " onCallAudioStateChanged: " + state);
Log.d(TAG, "onCallAudioStateChanged: " + state);
AudioModeModule audioModeModule
= ReactInstanceManagerHolder
.getNativeModule(AudioModeModule.class);
@@ -418,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);

View File

@@ -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

View File

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

View File

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

View File

@@ -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();
}
}
}

View File

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

View File

@@ -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

View File

@@ -1,394 +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) {
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 (token == null && 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.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;
}
}

View File

@@ -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());
}
}

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
/*
* Copyright @ 2018-present 8x8, Inc.
* Copyright @ 2017-2018 Atlassian Pty Ltd
* 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.
@@ -19,19 +18,20 @@ package org.jitsi.meet.sdk;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import com.facebook.react.bridge.ReadableMap;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import org.jitsi.meet.sdk.invite.InviteController;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Map;
public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener>
implements OngoingConferenceTracker.OngoingConferenceListener {
public class JitsiMeetView
extends BaseReactView<JitsiMeetViewListener> {
/**
* The {@code Method}s of {@code JitsiMeetViewListener} by event name i.e.
@@ -40,6 +40,57 @@ public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener>
private static final Map<String, Method> LISTENER_METHODS
= ListenerUtils.mapListenerMethods(JitsiMeetViewListener.class);
/**
* The {@link Log} tag which identifies the source of the log messages of
* {@code JitsiMeetView}.
*/
private static final String TAG = JitsiMeetView.class.getSimpleName();
/**
* Loads a specific URL {@code String} in all existing
* {@code JitsiMeetView}s.
*
* @param urlString he URL {@code String} to load in all existing
* {@code JitsiMeetView}s.
* @return If the specified {@code urlString} was submitted for loading in
* at least one {@code JitsiMeetView}, then {@code true}; otherwise,
* {@code false}.
*/
public static boolean loadURLStringInViews(String urlString) {
boolean loaded = false;
synchronized (views) {
for (BaseReactView view : views) {
if (view instanceof JitsiMeetView) {
((JitsiMeetView)view).loadURLString(urlString);
loaded = true;
}
}
}
return loaded;
}
/**
* The default base {@code URL} used to join a conference when a partial URL
* (e.g. a room name only) is specified to {@link #loadURLString(String)} or
* {@link #loadURLObject(Bundle)}.
*/
private URL defaultURL;
/**
* The entry point into the invite feature of Jitsi Meet. The Java
* counterpart of the JavaScript {@code InviteButton}.
*/
private final InviteController inviteController;
/**
* Whether Picture-in-Picture is enabled. If {@code null}, defaults to
* {@code true} iff the Android platform supports Picture-in-Picture
* natively.
*/
private Boolean pictureInPictureEnabled;
/**
* The URL of the current conference.
*/
@@ -48,68 +99,22 @@ public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener>
private volatile String url;
/**
* Helper method to recursively merge 2 {@link Bundle} objects representing React Native props.
*
* @param a - The first {@link Bundle}.
* @param b - The second {@link Bundle}.
* @return The merged {@link Bundle} object.
* Whether the Welcome page is enabled.
*/
private static Bundle mergeProps(@Nullable Bundle a, @Nullable Bundle b) {
Bundle result = new Bundle();
if (a == null) {
if (b != null) {
result.putAll(b);
}
return result;
}
if (b == null) {
result.putAll(a);
return result;
}
// Start by putting all of a in the result.
result.putAll(a);
// Iterate over each key in b and override if appropriate.
for (String key : b.keySet()) {
Object bValue = b.get(key);
Object aValue = a.get(key);
String valueType = bValue.getClass().getSimpleName();
if (valueType.contentEquals("Boolean")) {
result.putBoolean(key, (Boolean)bValue);
} else if (valueType.contentEquals("String")) {
result.putString(key, (String)bValue);
} else if (valueType.contentEquals("Bundle")) {
result.putBundle(key, mergeProps((Bundle)aValue, (Bundle)bValue));
} else {
throw new RuntimeException("Unsupported type: " + valueType);
}
}
return result;
}
private boolean welcomePageEnabled;
public JitsiMeetView(@NonNull Context context) {
super(context);
// The entry point into the invite feature of Jitsi Meet. The Java
// counterpart of the JavaScript InviteButton.
inviteController = new InviteController(externalAPIScope);
// Check if the parent Activity implements JitsiMeetActivityInterface,
// otherwise things may go wrong.
if (!(context instanceof JitsiMeetActivityInterface)) {
throw new RuntimeException("Enclosing Activity must implement JitsiMeetActivityInterface");
}
OngoingConferenceTracker.getInstance().addListener(this);
}
@Override
public void dispose() {
OngoingConferenceTracker.getInstance().removeListener(this);
super.dispose();
}
/**
@@ -122,72 +127,200 @@ public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener>
* page.
*/
public void enterPictureInPicture() {
PictureInPictureModule pipModule
= ReactInstanceManagerHolder.getNativeModule(
if (isPictureInPictureEnabled() && getURL() != null) {
PictureInPictureModule pipModule
= ReactInstanceManagerHolder.getNativeModule(
PictureInPictureModule.class);
if (pipModule != null
&& PictureInPictureModule.isPictureInPictureSupported()
&& !JitsiMeetActivityDelegate.arePermissionsBeingRequested()
&& this.url != null) {
try {
pipModule.enterPictureInPicture();
} catch (RuntimeException re) {
JitsiMeetLogger.e(re, "Failed to enter PiP mode");
if (pipModule != null) {
try {
pipModule.enterPictureInPicture();
} catch (RuntimeException re) {
Log.e(TAG, "onUserLeaveHint: failed to enter PiP mode", re);
}
}
}
}
/**
* Joins the conference specified by the given {@link JitsiMeetConferenceOptions}. If there is
* already an active conference, it will be left and the new one will be joined.
* @param options - Description of what conference must be joined and what options will be used
* when doing so.
* Gets the default base {@code URL} used to join a conference when a
* partial URL (e.g. a room name only) is specified to
* {@link #loadURLString(String)} or {@link #loadURLObject(Bundle)}. If not
* set or if set to {@code null}, the default built in JavaScript is used:
* https://meet.jit.si
*
* @return The default base {@code URL} or {@code null}.
*/
public void join(@Nullable JitsiMeetConferenceOptions options) {
setProps(options != null ? options.asProps() : new Bundle());
public URL getDefaultURL() {
return defaultURL;
}
/**
* Leaves the currently active conference.
* Gets the {@link InviteController} which represents the entry point into
* the invite feature of Jitsi Meet and is the Java counterpart of the
* JavaScript {@code InviteButton}.
*
* @return the {@link InviteController} which represents the entry point
* into the invite feature of Jitsi Meet and is the Java counterpart of the
* JavaScript {@code InviteButton}
*/
public void leave() {
setProps(new Bundle());
public InviteController getInviteController() {
return inviteController;
}
/**
* Helper method to set the React Native props.
* @param newProps - New props to be set on the React Native view.
* Gets the URL of the current conference.
*
* XXX The method is meant for internal purposes only at the time of this
* writing because there is no equivalent API on iOS.
*
* @return the URL {@code String} of the current conference if any;
* otherwise, {@code null}.
*/
private void setProps(@NonNull Bundle newProps) {
// Merge the default options with the newly provided ones.
Bundle props = mergeProps(JitsiMeet.getDefaultProps(), newProps);
String getURL() {
return url;
}
// XXX The setProps() method is supposed to be imperative i.e.
/**
* Gets whether Picture-in-Picture is enabled. Picture-in-Picture is
* natively supported on Android API >= 26 (Oreo), so it should not be
* enabled on older platform versions.
*
* @return If Picture-in-Picture is enabled, {@code true}; {@code false},
* otherwise.
*/
public boolean isPictureInPictureEnabled() {
return
PictureInPictureModule.isPictureInPictureSupported()
&& (pictureInPictureEnabled == null
|| pictureInPictureEnabled);
}
/**
* Gets whether the Welcome page is enabled. If {@code true}, the Welcome
* page is rendered when this {@code JitsiMeetView} is not at a URL
* identifying a Jitsi Meet conference/room.
*
* @return {@code true} if the Welcome page is enabled; otherwise,
* {@code false}.
*/
public boolean isWelcomePageEnabled() {
return welcomePageEnabled;
}
/**
* Loads a specific {@link URL} which may identify a conference to join. If
* the specified {@code URL} is {@code null} and the Welcome page is
* enabled, the Welcome page is displayed instead.
*
* @param url The {@code URL} to load which may identify a conference to
* join.
*/
public void loadURL(@Nullable URL url) {
loadURLString(url == null ? null : url.toString());
}
/**
* Loads a specific URL which may identify a conference to join. The URL is
* specified in the form of a {@link Bundle} of properties which (1)
* internally are sufficient to construct a URL {@code String} while (2)
* abstracting the specifics of constructing the URL away from API
* clients/consumers. If the specified URL is {@code null} and the Welcome
* page is enabled, the Welcome page is displayed instead.
*
* @param urlObject The URL to load which may identify a conference to join.
*/
public void loadURLObject(@Nullable Bundle urlObject) {
Bundle props = new Bundle();
// defaultURL
if (defaultURL != null) {
props.putString("defaultURL", defaultURL.toString());
}
// inviteController
InviteController inviteController = getInviteController();
if (inviteController != null) {
props.putBoolean(
"addPeopleEnabled",
inviteController.isAddPeopleEnabled());
props.putBoolean(
"dialOutEnabled",
inviteController.isDialOutEnabled());
}
// pictureInPictureEnabled
props.putBoolean(
"pictureInPictureEnabled",
isPictureInPictureEnabled());
// url
if (urlObject != null) {
props.putBundle("url", urlObject);
}
// welcomePageEnabled
props.putBoolean("welcomePageEnabled", welcomePageEnabled);
// XXX The method loadURLObject: is supposed to be imperative i.e.
// a second invocation with one and the same URL is expected to join
// the respective conference again if the first invocation was followed
// by leaving the conference. However, React and, respectively,
// appProperties/initialProperties are declarative expressions i.e. one
// and the same URL will not trigger an automatic re-render in the
// JavaScript source code. The workaround implemented bellow introduces
// "imperativeness" in React Component props by defining a unique value
// per setProps() invocation.
// imperativeness in React Component props by defining a unique value
// per loadURLObject: invocation.
props.putLong("timestamp", System.currentTimeMillis());
createReactRootView("App", props);
}
/**
* Handler for {@link OngoingConferenceTracker} events.
* @param conferenceUrl
* Loads a specific URL {@link String} which may identify a conference to
* join. If the specified URL {@code String} is {@code null} and the Welcome
* page is enabled, the Welcome page is displayed instead.
*
* @param urlString The URL {@code String} to load which may identify a
* conference to join.
*/
@Override
public void onCurrentConferenceChanged(String conferenceUrl) {
// This property was introduced in order to address
// an exception in the Picture-in-Picture functionality which arose
// because of delays related to bridging between JavaScript and Java. To
// reduce these delays do not wait for the call to be transferred to the
// UI thread.
this.url = conferenceUrl;
public void loadURLString(@Nullable String urlString) {
Bundle urlObject;
if (urlString == null) {
urlObject = null;
} else {
urlObject = new Bundle();
urlObject.putString("url", urlString);
}
loadURLObject(urlObject);
}
/**
* The internal processing for the URL of the current conference set on the
* associated {@link JitsiMeetView}.
*
* @param eventName the name of the external API event to be processed
* @param eventData the details/specifics of the event to process determined
* by/associated with the specified {@code eventName}.
*/
private void maybeSetViewURL(String eventName, ReadableMap eventData) {
switch(eventName) {
case "CONFERENCE_WILL_JOIN":
setURL(eventData.getString("url"));
break;
case "CONFERENCE_FAILED":
case "CONFERENCE_WILL_LEAVE":
case "LOAD_CONFIG_ERROR":
String url = eventData.getString("url");
if (url != null && url.equals(getURL())) {
setURL(null);
}
break;
}
}
/**
@@ -198,7 +331,63 @@ public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener>
* by/associated with the specified {@code name}.
*/
@Override
protected void onExternalAPIEvent(String name, ReadableMap data) {
public void onExternalAPIEvent(String name, ReadableMap data) {
// XXX The JitsiMeetView property URL was introduced in order to address
// an exception in the Picture-in-Picture functionality which arose
// because of delays related to bridging between JavaScript and Java. To
// reduce these delays do not wait for the call to be transferred to the
// UI thread.
maybeSetViewURL(name, data);
onExternalAPIEvent(LISTENER_METHODS, name, data);
}
/**
* Sets the default base {@code URL} used to join a conference when a
* partial URL (e.g. a room name only) is specified to
* {@link #loadURLString(String)} or {@link #loadURLObject(Bundle)}. Must be
* called before {@link #loadURL(URL)} for it to take effect.
*
* @param defaultURL The {@code URL} to be set as the default base URL.
* @see #getDefaultURL()
*/
public void setDefaultURL(URL defaultURL) {
this.defaultURL = defaultURL;
}
/**
* Sets whether Picture-in-Picture is enabled. Because Picture-in-Picture is
* natively supported only since certain platform versions, specifying
* {@code true} will have no effect on unsupported platform versions.
*
* @param pictureInPictureEnabled To enable Picture-in-Picture,
* {@code true}; otherwise, {@code false}.
*/
public void setPictureInPictureEnabled(boolean pictureInPictureEnabled) {
this.pictureInPictureEnabled = pictureInPictureEnabled;
}
/**
* Sets the URL of the current conference.
*
* XXX The method is meant for internal purposes only. It does not
* {@code loadURL}, it merely remembers the specified URL.
*
* @param url the URL {@code String} which to be set as the URL of the
* current conference.
*/
void setURL(String url) {
this.url = url;
}
/**
* Sets whether the Welcome page is enabled. Must be called before
* {@link #loadURL(URL)} for it to take effect.
*
* @param welcomePageEnabled {@code true} to enable the Welcome page;
* otherwise, {@code false}.
*/
public void setWelcomePageEnabled(boolean welcomePageEnabled) {
this.welcomePageEnabled = welcomePageEnabled;
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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.
* 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 java.util.Map;
/**
* Implements {@link JitsiMeetViewListener} so apps don't have to add stubs for
* all methods in the interface if they are only interested in some.
*/
public abstract class JitsiMeetViewAdapter
implements JitsiMeetViewListener {
@Override
public void onConferenceFailed(Map<String, Object> data) {
}
@Override
public void onConferenceJoined(Map<String, Object> data) {
}
@Override
public void onConferenceLeft(Map<String, Object> data) {
}
@Override
public void onConferenceWillJoin(Map<String, Object> data) {
}
@Override
public void onConferenceWillLeave(Map<String, Object> data) {
}
@Override
public void onLoadConfigError(Map<String, Object> data) {
}
}

View File

@@ -22,6 +22,15 @@ import java.util.Map;
* Interface for listening to events coming from Jitsi Meet.
*/
public interface JitsiMeetViewListener {
/**
* Called when joining a conference fails or an ongoing conference is
* interrupted due to a failure.
*
* @param data Map with an "error" key describing the problem, and a "url"
* key with the conference URL.
*/
void onConferenceFailed(Map<String, Object> data);
/**
* Called when a conference was joined.
*
@@ -30,16 +39,11 @@ public interface JitsiMeetViewListener {
void onConferenceJoined(Map<String, Object> data);
/**
* Called when the active conference ends, be it because of user choice or
* because of a failure.
* Called when the conference was left, typically after hanging up.
*
* @param data Map with 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 possible values for "error" are described here:
* https://github.com/jitsi/lib-jitsi-meet/blob/master/JitsiConnectionErrors.js
* https://github.com/jitsi/lib-jitsi-meet/blob/master/JitsiConferenceErrors.js
* @param data Map with a "url" key with the conference URL.
*/
void onConferenceTerminated(Map<String, Object> data);
void onConferenceLeft(Map<String, Object> data);
/**
* Called before the conference is joined.
@@ -47,4 +51,21 @@ public interface JitsiMeetViewListener {
* @param data Map with a "url" key with the conference URL.
*/
void onConferenceWillJoin(Map<String, Object> data);
/**
* Called before the conference is left.
*
* @param data Map with a "url" key with the conference URL.
*/
void onConferenceWillLeave(Map<String, Object> data);
/**
* Called when loading the main configuration file from the Jitsi Meet
* deployment fails.
*
* @param data Map with an "error" key with the error and a "url" key with
* the conference URL which necessitated the loading of the configuration
* file.
*/
void onLoadConfigError(Map<String, Object> data);
}

View File

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

View File

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

View File

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

View File

@@ -20,23 +20,18 @@ import android.annotation.TargetApi;
import android.app.Activity;
import android.app.PictureInPictureParams;
import android.os.Build;
import android.util.Log;
import android.util.Rational;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
@ReactModule(name = PictureInPictureModule.NAME)
class PictureInPictureModule
extends ReactContextBaseJavaModule {
public static final String NAME = "PictureInPicture";
private static final String TAG = NAME;
private final static String TAG = "PictureInPicture";
static boolean isPictureInPictureSupported() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
@@ -71,7 +66,7 @@ class PictureInPictureModule
throw new IllegalStateException("No current Activity!");
}
JitsiMeetLogger.i(TAG + " Entering Picture-in-Picture");
Log.d(TAG, "Entering Picture-in-Picture");
PictureInPictureParams.Builder builder
= new PictureInPictureParams.Builder()
@@ -106,6 +101,6 @@ class PictureInPictureModule
@Override
public String getName() {
return NAME;
return TAG;
}
}

View File

@@ -24,7 +24,6 @@ import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.module.annotations.ReactModule;
/**
* Module implementing a simple API to enable a proximity sensor-controlled
@@ -32,11 +31,14 @@ import com.facebook.react.module.annotations.ReactModule;
* object it will dim the screen and disable touch controls. The functionality
* is used with the conference audio-only mode.
*/
@ReactModule(name = ProximityModule.NAME)
class ProximityModule
extends ReactContextBaseJavaModule {
public static final String NAME = "Proximity";
/**
* The name of {@code ProximityModule} to be used in the React Native
* bridge.
*/
private static final String MODULE_NAME = "Proximity";
/**
* This type of wake lock (the one activated by the proximity sensor) has
@@ -72,7 +74,7 @@ class ProximityModule
wakeLock
= powerManager.newWakeLock(
PROXIMITY_SCREEN_OFF_WAKE_LOCK,
"jitsi:"+NAME);
MODULE_NAME);
} catch (Throwable ignored) {
wakeLock = null;
}
@@ -87,7 +89,7 @@ class ProximityModule
*/
@Override
public String getName() {
return NAME;
return MODULE_NAME;
}
/**

View File

@@ -5,21 +5,19 @@ import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.RequiresApi;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import androidx.annotation.RequiresApi;
import android.util.Log;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
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;
/**
* The react-native side of Jitsi Meet's {@link ConnectionService}. Exposes
@@ -28,13 +26,10 @@ import org.jitsi.meet.sdk.log.JitsiMeetLogger;
* @author Pawel Domas
*/
@RequiresApi(api = Build.VERSION_CODES.O)
@ReactModule(name = RNConnectionService.NAME)
class RNConnectionService
extends ReactContextBaseJavaModule {
public static final String NAME = "ConnectionService";
private static final String TAG = ConnectionService.TAG;
private final static String TAG = ConnectionService.TAG;
/**
* Sets the audio route on all existing {@link android.telecom.Connection}s
@@ -75,11 +70,11 @@ class RNConnectionService
String handle,
boolean hasVideo,
Promise promise) {
JitsiMeetLogger.d("%s startCall UUID=%s, h=%s, v=%s",
TAG,
Log.d(TAG,
String.format("startCall UUID=%s, h=%s, v=%s",
callUUID,
handle,
hasVideo);
hasVideo));
ReactApplicationContext ctx = getReactApplicationContext();
@@ -119,7 +114,7 @@ class RNConnectionService
*/
@ReactMethod
public void reportCallFailed(String callUUID) {
JitsiMeetLogger.d(TAG + " reportCallFailed " + callUUID);
Log.d(TAG, "reportCallFailed " + callUUID);
ConnectionService.setConnectionDisconnected(
callUUID,
new DisconnectCause(DisconnectCause.ERROR));
@@ -132,7 +127,7 @@ class RNConnectionService
*/
@ReactMethod
public void endCall(String callUUID) {
JitsiMeetLogger.d(TAG + " endCall " + callUUID);
Log.d(TAG, "endCall " + callUUID);
ConnectionService.setConnectionDisconnected(
callUUID,
new DisconnectCause(DisconnectCause.LOCAL));
@@ -144,15 +139,14 @@ class RNConnectionService
* @param callUUID - the call's UUID.
*/
@ReactMethod
public void reportConnectedOutgoingCall(String callUUID, Promise promise) {
JitsiMeetLogger.d(TAG + " reportConnectedOutgoingCall " + callUUID);
public void reportConnectedOutgoingCall(String callUUID) {
Log.d(TAG, "reportConnectedOutgoingCall " + callUUID);
ConnectionService.setConnectionActive(callUUID);
promise.resolve(null);
}
@Override
public String getName() {
return NAME;
return "ConnectionService";
}
/**

View File

@@ -25,7 +25,6 @@ import android.os.Build;
import com.calendarevents.CalendarEventsPackage;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.core.PermissionListener;
/**
@@ -33,22 +32,7 @@ import com.facebook.react.modules.core.PermissionListener;
* {@link Activity} lifecycle methods in order for the React side to be aware of
* it.
*/
public class JitsiMeetActivityDelegate {
/**
* Needed for making sure this class working with the "PermissionsAndroid"
* React Native module.
*/
private static PermissionListener permissionListener;
private static Callback permissionsCallback;
/**
* Tells whether or not the permissions request is currently in progress.
*
* @return {@code true} if the permssions are being requested or {@code false} otherwise.
*/
static boolean arePermissionsBeingRequested() {
return permissionListener != null;
}
public class ReactActivityLifecycleCallbacks {
/**
* {@link Activity} lifecycle method which should be called from
@@ -73,6 +57,13 @@ public class JitsiMeetActivityDelegate {
}
}
/**
* Needed for making sure this class working with the "PermissionsAndroid"
* React Native module.
*/
private static PermissionListener permissionListener;
private static Callback permissionsCallback;
/**
* {@link Activity} lifecycle method which should be called from
* {@link Activity#onBackPressed} so we can do the required internal
@@ -118,13 +109,7 @@ public class JitsiMeetActivityDelegate {
= ReactInstanceManagerHolder.getReactInstanceManager();
if (reactInstanceManager != null) {
// Try to avoid a crash because some devices trip on this assert:
// https://github.com/facebook/react-native/blob/df4e67fe75d781d1eb264128cadf079989542755/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java#L512
// Why this happens is a mystery wrapped in an enigma.
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
if (reactContext != null && activity == reactContext.getCurrentActivity()) {
reactInstanceManager.onHostPause(activity);
}
reactInstanceManager.onHostPause(activity);
}
}

View File

@@ -0,0 +1,43 @@
/*
* 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.
* 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.support.annotation.Nullable;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.core.DeviceEventManagerModule;
public class ReactContextUtils {
public static boolean emitEvent(
ReactContext reactContext,
String eventName,
@Nullable Object data) {
if (reactContext == null) {
// XXX If no ReactContext is specified, emit through the
// ReactContext of ReactInstanceManager. ReactInstanceManager
// cooperates with ReactContextUtils i.e. ReactInstanceManager will
// not invoke ReactContextUtils without a ReactContext.
return ReactInstanceManagerHolder.emitEvent(eventName, data);
}
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, data);
return true;
}
}

View File

@@ -1,6 +1,5 @@
/*
* Copyright @ 2019-present 8x8, Inc.
* Copyright @ 2017-2018 Atlassian Pty Ltd
* 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.
@@ -17,32 +16,15 @@
package org.jitsi.meet.sdk;
import android.app.Activity;
import androidx.annotation.Nullable;
import android.app.Application;
import android.support.annotation.Nullable;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.devsupport.DevInternalSettings;
import com.facebook.react.jscexecutor.JSCExecutorFactory;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.soloader.SoLoader;
import com.oney.WebRTCModule.RTCVideoViewManager;
import com.oney.WebRTCModule.WebRTCModule;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import org.webrtc.SoftwareVideoDecoderFactory;
import org.webrtc.SoftwareVideoEncoderFactory;
import org.webrtc.VideoDecoderFactory;
import org.webrtc.VideoEncoderFactory;
import org.webrtc.audio.AudioDeviceModule;
import org.webrtc.audio.JavaAudioDeviceModule;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -59,66 +41,36 @@ class ReactInstanceManagerHolder {
*/
private static ReactInstanceManager reactInstanceManager;
private static List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
private static List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> nativeModules
= new ArrayList<>(Arrays.<NativeModule>asList(
new AndroidSettingsModule(reactContext),
new AppInfoModule(reactContext),
new AudioModeModule(reactContext),
new DropboxModule(reactContext),
new ExternalAPIModule(reactContext),
new JavaScriptSandboxModule(reactContext),
new LocaleDetector(reactContext),
new LogBridgeModule(reactContext),
new PictureInPictureModule(reactContext),
new ProximityModule(reactContext),
new WiFiStatsModule(reactContext),
new org.jitsi.meet.sdk.dropbox.Dropbox(reactContext),
new org.jitsi.meet.sdk.invite.InviteModule(reactContext),
new org.jitsi.meet.sdk.net.NAT64AddrInfoModule(reactContext)));
if (AudioModeModule.useConnectionService()) {
nativeModules.add(new RNConnectionService(reactContext));
}
// Initialize the WebRTC module by hand, since we want to override some
// initialization options.
WebRTCModule.Options options = new WebRTCModule.Options();
AudioDeviceModule adm = JavaAudioDeviceModule.builder(reactContext)
.createAudioDeviceModule();
VideoDecoderFactory videoDecoderFactory = new SoftwareVideoDecoderFactory();
VideoEncoderFactory videoEncoderFactory = new SoftwareVideoEncoderFactory();
options.setAudioDeviceModule(adm);
options.setVideoDecoderFactory(videoDecoderFactory);
options.setVideoEncoderFactory(videoEncoderFactory);
nativeModules.add(new WebRTCModule(reactContext, options));
try {
Class<?> amplitudeModuleClass = Class.forName("org.jitsi.meet.sdk.AmplitudeModule");
Constructor constructor = amplitudeModuleClass.getConstructor(ReactApplicationContext.class);
nativeModules.add((NativeModule)constructor.newInstance(reactContext));
} catch (Exception e) {
// Ignore any error, the module is not compiled when LIBRE_BUILD is enabled.
}
return nativeModules;
}
private static List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
// WebRTC, see createNativeModules for details.
new RTCVideoViewManager()
);
}
/**
* Helper function to send an event to JavaScript.
*
* @param eventName {@code String} containing the event name.
* @param data {@code Object} optional ancillary data for the event.
*/
static void emitEvent(
static boolean emitEvent(
String eventName,
@Nullable Object data) {
ReactInstanceManager reactInstanceManager
@@ -128,12 +80,15 @@ class ReactInstanceManagerHolder {
ReactContext reactContext
= reactInstanceManager.getCurrentReactContext();
if (reactContext != null) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, data);
}
return
reactContext != null
&& ReactContextUtils.emitEvent(
reactContext,
eventName,
data);
}
return false;
}
/**
@@ -156,18 +111,6 @@ class ReactInstanceManagerHolder {
? reactContext.getNativeModule(nativeModuleClass) : null;
}
/**
* Gets the current {@link Activity} linked to React Native.
*
* @return An activity attached to React Native.
*/
static Activity getCurrentActivity() {
ReactContext reactContext
= reactInstanceManager != null
? reactInstanceManager.getCurrentReactContext() : null;
return reactContext != null ? reactContext.getCurrentActivity() : null;
}
static ReactInstanceManager getReactInstanceManager() {
return reactInstanceManager;
}
@@ -178,71 +121,40 @@ class ReactInstanceManagerHolder {
* time. All {@code ReactRootView} instances will be tied to the one and
* only {@code ReactInstanceManager}.
*
* @param activity {@code Activity} current running Activity.
* @param application {@code Application} instance which is running.
*/
static void initReactInstanceManager(Activity activity) {
static void initReactInstanceManager(Application application) {
if (reactInstanceManager != null) {
return;
}
SoLoader.init(activity, /* native exopackage */ false);
List<ReactPackage> packages
= new ArrayList<>(Arrays.asList(
new com.BV.LinearGradient.LinearGradientPackage(),
new com.calendarevents.CalendarEventsPackage(),
new com.corbt.keepawake.KCKeepAwakePackage(),
new com.facebook.react.shell.MainReactPackage(),
new com.horcrux.svg.SvgPackage(),
new com.ocetnik.timer.BackgroundTimerPackage(),
new com.reactnativecommunity.asyncstorage.AsyncStoragePackage(),
new com.reactnativecommunity.netinfo.NetInfoPackage(),
new com.reactnativecommunity.webview.RNCWebViewPackage(),
new com.rnimmersive.RNImmersivePackage(),
new com.zmxv.RNSound.RNSoundPackage(),
new ReactPackageAdapter() {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return ReactInstanceManagerHolder.createNativeModules(reactContext);
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return ReactInstanceManagerHolder.createViewManagers(reactContext);
}
}));
try {
Class<?> googlePackageClass = Class.forName("co.apptailor.googlesignin.RNGoogleSigninPackage");
Constructor constructor = googlePackageClass.getConstructor();
packages.add((ReactPackage)constructor.newInstance());
} catch (Exception e) {
// Ignore any error, the module is not compiled when LIBRE_BUILD is enabled.
}
// Keep on using JSC, the jury is out on Hermes.
JSCExecutorFactory jsFactory
= new JSCExecutorFactory("", "");
reactInstanceManager
= ReactInstanceManager.builder()
.setApplication(activity.getApplication())
.setCurrentActivity(activity)
.setApplication(application)
.setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index.android")
.setJavaScriptExecutorFactory(jsFactory)
.addPackages(packages)
.addPackage(new co.apptailor.googlesignin.RNGoogleSigninPackage())
.addPackage(new com.BV.LinearGradient.LinearGradientPackage())
.addPackage(new com.calendarevents.CalendarEventsPackage())
.addPackage(new com.corbt.keepawake.KCKeepAwakePackage())
.addPackage(new com.dylanvann.fastimage.FastImageViewPackage())
.addPackage(new com.facebook.react.shell.MainReactPackage())
.addPackage(new com.oblador.vectoricons.VectorIconsPackage())
.addPackage(new com.ocetnik.timer.BackgroundTimerPackage())
.addPackage(new com.oney.WebRTCModule.WebRTCModulePackage())
.addPackage(new com.rnimmersive.RNImmersivePackage())
.addPackage(new com.zmxv.RNSound.RNSoundPackage())
.addPackage(new ReactPackageAdapter() {
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
return
ReactInstanceManagerHolder.createNativeModules(
reactContext);
}
})
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
// Disable delta updates on Android, they have caused trouble.
DevInternalSettings devSettings
= (DevInternalSettings)reactInstanceManager.getDevSupportManager().getDevSettings();
if (devSettings != null) {
devSettings.setBundleDeltasEnabled(false);
}
// Register our uncaught exception handler.
JitsiMeetUncaughtExceptionHandler.register();
}
}

View File

@@ -19,14 +19,13 @@ package org.jitsi.meet.sdk;
import android.content.Context;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.util.Log;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import org.json.JSONArray;
import org.json.JSONObject;
@@ -44,16 +43,19 @@ import java.util.concurrent.Executors;
* Gathers rssi, signal in percentage, timestamp and the addresses of the wifi
* device.
*/
@ReactModule(name = WiFiStatsModule.NAME)
class WiFiStatsModule
extends ReactContextBaseJavaModule {
public static final String NAME = "WiFiStats";
/**
* The name of {@code WiFiStatsModule} to be used in the React Native
* bridge.
*/
private static final String MODULE_NAME = "WiFiStats";
/**
* The {@code Log} tag {@code WiFiStatsModule} is to log messages with.
*/
static final String TAG = NAME;
static final String TAG = MODULE_NAME;
/**
* The scale used for the signal value. A level of the signal, given in the
@@ -85,7 +87,7 @@ class WiFiStatsModule
*/
@Override
public String getName() {
return NAME;
return MODULE_NAME;
}
/**
@@ -144,7 +146,8 @@ class WiFiStatsModule
JSONObject result = new JSONObject();
result.put("rssi", rssi)
.put("signal", signalLevel)
.put("timestamp", System.currentTimeMillis());
.put("timestamp",
String.valueOf(System.currentTimeMillis()));
JSONArray addresses = new JSONArray();
@@ -184,15 +187,17 @@ class WiFiStatsModule
}
} catch (SocketException e) {
JitsiMeetLogger.e(e, TAG + " Unable to NetworkInterface.getNetworkInterfaces()");
Log.wtf(TAG,
"Unable to NetworkInterface.getNetworkInterfaces()"
);
}
result.put("addresses", addresses);
promise.resolve(result.toString());
JitsiMeetLogger.d(TAG + " WiFi stats: " + result.toString());
Log.d(TAG, "WiFi stats: " + result.toString());
} catch (Throwable e) {
JitsiMeetLogger.e(e, TAG + " Failed to obtain wifi stats");
Log.e(TAG, "Failed to obtain wifi stats", e);
promise.reject(
new Exception("Failed to obtain wifi stats"));
}

View File

@@ -1,4 +1,4 @@
package org.jitsi.meet.sdk;
package org.jitsi.meet.sdk.dropbox;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -20,7 +20,6 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.dropbox.core.android.Auth;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.module.annotations.ReactModule;
import java.util.HashMap;
import java.util.Map;
@@ -28,13 +27,9 @@ import java.util.Map;
/**
* Implements the react-native module for the dropbox integration.
*/
@ReactModule(name = DropboxModule.NAME)
class DropboxModule
public class Dropbox
extends ReactContextBaseJavaModule
implements LifecycleEventListener {
public static final String NAME = "Dropbox";
private String appKey;
private String clientId;
@@ -43,7 +38,7 @@ class DropboxModule
private Promise promise;
public DropboxModule(ReactApplicationContext reactContext) {
public Dropbox(ReactApplicationContext reactContext) {
super(reactContext);
String pkg = reactContext.getApplicationContext().getPackageName();
@@ -136,7 +131,7 @@ class DropboxModule
@Override
public String getName() {
return NAME;
return "Dropbox";
}
/**

View File

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

View File

@@ -18,7 +18,7 @@ package org.jitsi.meet.sdk.incoming_call;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import android.support.annotation.NonNull;
import com.facebook.react.bridge.ReadableMap;
@@ -50,7 +50,7 @@ public class IncomingCallView
* by/associated with the specified {@code name}.
*/
@Override
protected void onExternalAPIEvent(String name, ReadableMap data) {
public void onExternalAPIEvent(String name, ReadableMap data) {
onExternalAPIEvent(LISTENER_METHODS, name, data);
}

View File

@@ -0,0 +1,211 @@
/*
* 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.
* 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.invite;
import android.util.Log;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.bridge.WritableNativeMap;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* Controller object used by native code to query and submit user selections for
* the user invitation flow.
*/
public class AddPeopleController {
/**
* The AddPeopleControllerListener for this controller, used to pass query
* results back to the native code that initiated the query.
*/
private AddPeopleControllerListener listener;
/**
* Local cache of search query results. Used to re-hydrate the list of
* selected items based on their ids passed to inviteById in order to pass
* the full item maps back to the JitsiMeetView during submission.
*/
private final Map<String, ReadableMap> items = new HashMap<>();
private final WeakReference<InviteController> owner;
private final WeakReference<ReactApplicationContext> reactContext;
/**
* Randomly generated UUID, used for identification in the InviteModule.
*/
private final String uuid = UUID.randomUUID().toString();
public AddPeopleController(
InviteController owner,
ReactApplicationContext reactContext) {
this.owner = new WeakReference<>(owner);
this.reactContext = new WeakReference<>(reactContext);
}
/**
* Cancel the invitation flow and free memory allocated to the
* AddPeopleController. After calling this method, this object is invalid -
* a new AddPeopleController will be passed to the caller through
* beginAddPeople.
*/
public void endAddPeople() {
InviteController owner = this.owner.get();
if (owner != null) {
owner.endAddPeople(this);
}
}
/**
*
* @return the AddPeopleControllerListener for this controller, used to pass
* query results back to the native code that initiated the query.
*/
public AddPeopleControllerListener getListener() {
return listener;
}
final ReactApplicationContext getReactApplicationContext() {
return reactContext.get();
}
/**
*
* @return the unique identifier for this AddPeopleController
*/
public String getUuid() {
return uuid;
}
/**
* Send invites to selected users based on their item ids
*
* @param ids
*/
public void inviteById(List<String> ids) {
InviteController owner = this.owner.get();
if (owner != null) {
WritableArray invitees = new WritableNativeArray();
for(int i = 0, size = ids.size(); i < size; i++) {
String id = ids.get(i);
if(items.containsKey(id)) {
WritableNativeMap map = new WritableNativeMap();
map.merge(items.get(id));
invitees.pushMap(map);
} else {
// If the id doesn't exist in the map, we can't do anything,
// so just skip it.
}
}
owner.invite(this, invitees);
}
}
void inviteSettled(ReadableArray failedInvitees) {
AddPeopleControllerListener listener = getListener();
if (listener != null) {
ArrayList<Map<String, Object>> jFailedInvitees = new ArrayList<>();
for (int i = 0, size = failedInvitees.size(); i < size; ++i) {
jFailedInvitees.add(failedInvitees.getMap(i).toHashMap());
}
listener.onInviteSettled(this, jFailedInvitees);
}
}
/**
* Start a search for entities to invite with the given query. Results will
* be returned through the associated AddPeopleControllerListener's
* onReceivedResults method.
*
* @param query
*/
public void performQuery(String query) {
InviteController owner = this.owner.get();
if (owner != null) {
owner.performQuery(this, query);
}
}
/**
* Caches results received by the search into a local map for use later when
* the items are submitted. Submission requires the full map of
* information, but only the IDs are returned back to the delegate. Using
* this map means we don't have to send the whole map back to the delegate.
*
* @param results
* @param query
*/
void receivedResultsForQuery(ReadableArray results, String query) {
AddPeopleControllerListener listener = getListener();
if (listener != null) {
List<Map<String, Object>> jvmResults = new ArrayList<>();
// cache results for use in submission later
// convert to jvm array
for(int i = 0; i < results.size(); i++) {
ReadableMap map = results.getMap(i);
if(map.hasKey("id")) {
items.put(map.getString("id"), map);
} else if(map.hasKey("type")
&& map.getString("type").equals("phone")
&& map.hasKey("number")) {
items.put(map.getString("number"), map);
} else {
Log.w(
"AddPeopleController",
"Received result without id and that was not a phone number, so not adding it to suggestions: "
+ map);
}
jvmResults.add(map.toHashMap());
}
listener.onReceivedResults(this, jvmResults, query);
}
}
/**
* Sets the AddPeopleControllerListener for this controller, used to pass
* query results back to the native code that initiated the query.
*
* @param listener
*/
public void setListener(AddPeopleControllerListener listener) {
this.listener = listener;
}
}

View File

@@ -0,0 +1,56 @@
/*
* 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.
* 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.invite;
import java.util.List;
import java.util.Map;
public interface AddPeopleControllerListener {
/**
* Called when the call to {@link AddPeopleController#inviteById(List)}
* completes.
*
* @param addPeopleController the active {@link AddPeopleController} for
* this invite flow. This object should be cleaned up by calling
* {@link AddPeopleController#endAddPeople()} if the user exits the invite
* flow. Otherwise, it can stay active if the user will attempt to invite
* @param failedInvitees a {@code List} of {@code Map<String, Object>}
* dictionaries that represent the invitations that failed. The data type of
* the objects is identical to the results returned in onReceivedResuls.
*/
void onInviteSettled(
AddPeopleController addPeopleController,
List<Map<String, Object>> failedInvitees);
/**
* Called when results are received for a query called through
* AddPeopleController.query().
*
* @param addPeopleController
* @param results a List of Map<String, Object> objects that represent items
* returned by the query. The object at key "type" describes the type of
* item: "user", "videosipgw" (conference room), or "phone". "user" types
* have properties at "id", "name", and "avatar". "videosipgw" types have
* properties at "id" and "name". "phone" types have properties at "number",
* "title", "and "subtitle"
* @param query the query that generated the given results
*/
void onReceivedResults(
AddPeopleController addPeopleController,
List<Map<String, Object>> results,
String query);
}

View File

@@ -0,0 +1,265 @@
/*
* 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.
* 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.invite;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableNativeMap;
import org.jitsi.meet.sdk.ReactContextUtils;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
/**
* Represents the entry point into the invite feature of Jitsi Meet and is the
* Java counterpart of the JavaScript {@code InviteButton}.
*/
public class InviteController {
private AddPeopleController addPeopleController;
/**
* Whether adding/inviting people by name (as opposed to phone number) is
* enabled.
*/
private Boolean addPeopleEnabled;
/**
* Whether adding/inviting people by phone number (as opposed to name) is
* enabled.
*/
private Boolean dialOutEnabled;
private final String externalAPIScope;
private InviteControllerListener listener;
public InviteController(String externalAPIScope) {
this.externalAPIScope = externalAPIScope;
}
void beginAddPeople(ReactApplicationContext reactContext) {
InviteControllerListener listener = getListener();
if (listener != null) {
// XXX For the sake of simplicity and in order to reduce the risk of
// memory leaks, allow a single AddPeopleController at a time.
AddPeopleController addPeopleController = this.addPeopleController;
if (addPeopleController != null) {
return;
}
// Initialize a new AddPeopleController to represent the click/tap
// on the InviteButton and notify the InviteControllerListener
// about the event.
addPeopleController = new AddPeopleController(this, reactContext);
boolean success = false;
this.addPeopleController = addPeopleController;
try {
listener.beginAddPeople(addPeopleController);
success = true;
} finally {
if (!success) {
endAddPeople(addPeopleController);
}
}
}
}
void endAddPeople(AddPeopleController addPeopleController) {
if (this.addPeopleController == addPeopleController) {
this.addPeopleController = null;
}
}
public InviteControllerListener getListener() {
return listener;
}
/**
* Sends JavaScript event to submit invitations to the given item ids
*
* @param invitees a WritableArray of WritableNativeMaps representing
* selected items. Each map representing a selected item should match the
* data passed back in the return from a query.
*/
boolean invite(
AddPeopleController addPeopleController,
WritableArray invitees) {
return
invite(
addPeopleController.getUuid(),
addPeopleController.getReactApplicationContext(),
invitees);
}
public Future<List<Map<String, Object>>> invite(
final List<Map<String, Object>> invitees) {
final boolean inviteBegan
= invite(
UUID.randomUUID().toString(),
/* reactContext */ null,
Arguments.makeNativeArray(invitees));
FutureTask futureTask
= new FutureTask(new Callable() {
@Override
public List<Map<String, Object>> call() {
if (inviteBegan) {
// TODO Complete the returned Future when the invite
// settles.
return Collections.emptyList();
} else {
// The invite failed to even begin so report that all
// invitees failed.
return invitees;
}
}
});
// If the invite failed to even begin, complete the returned Future
// already and the Future implementation will report that all invitees
// failed.
if (!inviteBegan) {
futureTask.run();
}
return futureTask;
}
private boolean invite(
String addPeopleControllerScope,
ReactContext reactContext,
WritableArray invitees) {
WritableNativeMap data = new WritableNativeMap();
data.putString("addPeopleControllerScope", addPeopleControllerScope);
data.putString("externalAPIScope", externalAPIScope);
data.putArray("invitees", invitees);
return
ReactContextUtils.emitEvent(
reactContext,
"org.jitsi.meet:features/invite#invite",
data);
}
void inviteSettled(
String addPeopleControllerScope,
ReadableArray failedInvitees) {
AddPeopleController addPeopleController = this.addPeopleController;
if (addPeopleController != null
&& addPeopleController.getUuid().equals(
addPeopleControllerScope)) {
try {
addPeopleController.inviteSettled(failedInvitees);
} finally {
if (failedInvitees.size() == 0) {
endAddPeople(addPeopleController);
}
}
}
}
public boolean isAddPeopleEnabled() {
Boolean b = this.addPeopleEnabled;
return
(b == null || b.booleanValue()) ? (getListener() != null) : false;
}
public boolean isDialOutEnabled() {
Boolean b = this.dialOutEnabled;
return
(b == null || b.booleanValue()) ? (getListener() != null) : false;
}
/**
* Starts a query for users to invite to the conference. Results will be
* returned through
* {@link AddPeopleControllerListener#onReceivedResults(AddPeopleController, List, String)}.
*
* @param query {@code String} to use for the query
*/
void performQuery(AddPeopleController addPeopleController, String query) {
WritableNativeMap params = new WritableNativeMap();
params.putString("addPeopleControllerScope", addPeopleController.getUuid());
params.putString("externalAPIScope", externalAPIScope);
params.putString("query", query);
ReactContextUtils.emitEvent(
addPeopleController.getReactApplicationContext(),
"org.jitsi.meet:features/invite#performQuery",
params);
}
void receivedResultsForQuery(
String addPeopleControllerScope,
String query,
ReadableArray results) {
AddPeopleController addPeopleController = this.addPeopleController;
if (addPeopleController != null
&& addPeopleController.getUuid().equals(
addPeopleControllerScope)) {
addPeopleController.receivedResultsForQuery(results, query);
}
}
/**
* Sets whether the ability to add users to the call is enabled. If this is
* enabled, an add user button will appear on the {@link JitsiMeetView}. If
* enabled, and the user taps the add user button,
* {@link InviteControllerListener#beginAddPeople(AddPeopleController)}
* will be called.
*
* @param addPeopleEnabled {@code true} to enable the add people button;
* otherwise, {@code false}
*/
public void setAddPeopleEnabled(boolean addPeopleEnabled) {
this.addPeopleEnabled = Boolean.valueOf(addPeopleEnabled);
}
/**
* Sets whether the ability to add phone numbers to the call is enabled.
* Must be enabled along with {@link #setAddPeopleEnabled(boolean)} to be
* effective.
*
* @param dialOutEnabled {@code true} to enable the ability to add phone
* numbers to the call; otherwise, {@code false}
*/
public void setDialOutEnabled(boolean dialOutEnabled) {
this.dialOutEnabled = Boolean.valueOf(dialOutEnabled);
}
public void setListener(InviteControllerListener listener) {
this.listener = listener;
}
}

View File

@@ -1,6 +1,5 @@
/*
* Copyright @ 2018-present 8x8, Inc.
* Copyright @ 2017-2018 Atlassian Pty Ltd
* 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.
@@ -15,13 +14,16 @@
* limitations under the License.
*/
import WatchKit
package org.jitsi.meet.sdk.invite;
class MeetingRowController: NSObject {
@IBOutlet var roomLabel: WKInterfaceLabel!
@IBOutlet var timeLabel: WKInterfaceLabel!
@IBOutlet var rowGroup: WKInterfaceGroup!
var room: String!
var roomUrl: String!
public interface InviteControllerListener {
/**
* Called when the add user button is tapped.
*
* @param addPeopleController {@code AddPeopleController} scoped for this
* user invite flow. The {@code AddPeopleController} is used to start user
* queries and accepts an {@code AddPeopleControllerListener} for receiving
* user query responses.
*/
void beginAddPeople(AddPeopleController addPeopleController);
}

View File

@@ -0,0 +1,171 @@
/*
* 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.
* 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.invite;
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.ReadableArray;
import com.facebook.react.bridge.UiThreadUtil;
import org.jitsi.meet.sdk.BaseReactView;
import org.jitsi.meet.sdk.JitsiMeetView;
/**
* Implements the react-native module of the feature invite.
*/
public class InviteModule
extends ReactContextBaseJavaModule {
public InviteModule(ReactApplicationContext reactContext) {
super(reactContext);
}
/**
* Signals that a click/tap has been performed on {@code InviteButton} and
* that the execution flow for adding/inviting people to the current
* conference/meeting is to begin
*
* @param externalAPIScope the unique identifier of the
* {@code JitsiMeetView} whose {@code InviteButton} was clicked/tapped.
*/
@ReactMethod
public void beginAddPeople(final String externalAPIScope) {
// Make sure InviteControllerListener (like all other listeners of the
// SDK) is invoked on the UI thread. It was requested by SDK consumers.
if (!UiThreadUtil.isOnUiThread()) {
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
beginAddPeople(externalAPIScope);
}
});
return;
}
InviteController inviteController
= findInviteControllerByExternalAPIScope(externalAPIScope);
if (inviteController != null) {
inviteController.beginAddPeople(getReactApplicationContext());
}
}
private InviteController findInviteControllerByExternalAPIScope(
String externalAPIScope) {
JitsiMeetView view
= (JitsiMeetView)
BaseReactView.findViewByExternalAPIScope(externalAPIScope);
return view == null ? null : view.getInviteController();
}
@Override
public String getName() {
return "Invite";
}
/**
* Callback for invitation failures
*
* @param failedInvitees the items for which the invitation failed
* @param addPeopleControllerScope a string that represents a connection to
* a specific AddPeopleController
*/
@ReactMethod
public void inviteSettled(
final String externalAPIScope,
final String addPeopleControllerScope,
final ReadableArray failedInvitees) {
// Make sure AddPeopleControllerListener (like all other listeners of
// the SDK) is invoked on the UI thread. It was requested by SDK
// consumers.
if (!UiThreadUtil.isOnUiThread()) {
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
inviteSettled(
externalAPIScope,
addPeopleControllerScope,
failedInvitees);
}
});
return;
}
InviteController inviteController
= findInviteControllerByExternalAPIScope(externalAPIScope);
if (inviteController == null) {
Log.w(
"InviteModule",
"Invite settled, but failed to find active controller to notify");
} else {
inviteController.inviteSettled(
addPeopleControllerScope,
failedInvitees);
}
}
/**
* Callback for results received from the JavaScript invite search call
*
* @param results the results in a ReadableArray of ReadableMap objects
* @param query the query associated with the search
* @param addPeopleControllerScope a string that represents a connection to
* a specific AddPeopleController
*/
@ReactMethod
public void receivedResults(
final String externalAPIScope,
final String addPeopleControllerScope,
final String query,
final ReadableArray results) {
// Make sure AddPeopleControllerListener (like all other listeners of
// the SDK) is invoked on the UI thread. It was requested by SDK
// consumers.
if (!UiThreadUtil.isOnUiThread()) {
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
receivedResults(
externalAPIScope,
addPeopleControllerScope,
query,
results);
}
});
return;
}
InviteController inviteController
= findInviteControllerByExternalAPIScope(externalAPIScope);
if (inviteController == null) {
Log.w(
"InviteModule",
"Received results, but failed to find active controller to send results back");
} else {
inviteController.receivedResultsForQuery(
addPeopleControllerScope,
query,
results);
}
}
}

View File

@@ -1,49 +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.log;
import android.util.Log;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.text.MessageFormat;
import timber.log.Timber;
/**
* Base class for all custom log handlers. Implementations must inherit from this class and
* implement a `doLog` method which does the actual logging, in addition with a `getTag` method
* with which to tag all logs coming into this logger.
*
* See {@link JitsiMeetDefaultLogHandler} for an example.
*/
public abstract class JitsiMeetBaseLogHandler extends Timber.Tree {
@Override
protected void log(int priority, @Nullable String tag, @NotNull String msg, @Nullable Throwable t) {
String errmsg = Log.getStackTraceString(t);
if (errmsg.isEmpty()) {
doLog(priority, getDefaultTag(), msg);
} else {
doLog(priority, getDefaultTag(), MessageFormat.format("{0}\n{1}", msg, errmsg));
}
}
protected abstract void doLog(int priority, @NotNull String tag, @NotNull String msg);
protected abstract String getDefaultTag();
}

View File

@@ -1,39 +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.log;
import android.util.Log;
import org.jetbrains.annotations.NotNull;
/**
* Default implementation of a {@link JitsiMeetBaseLogHandler}. This is the main SDK logger, which
* logs using the Android util.Log module.
*/
public class JitsiMeetDefaultLogHandler extends JitsiMeetBaseLogHandler {
private static final String TAG = "JitsiMeetSDK";
@Override
protected void doLog(int priority, @NotNull String tag, @NotNull String msg) {
Log.println(priority, tag, msg);
}
@Override
protected String getDefaultTag() {
return TAG;
}
}

View File

@@ -1,94 +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.log;
import timber.log.Timber;
public class JitsiMeetLogger {
static {
addHandler(new JitsiMeetDefaultLogHandler());
}
public static void addHandler(JitsiMeetBaseLogHandler handler) {
Timber.plant(handler);
}
public static void removeHandler(JitsiMeetBaseLogHandler handler) {
Timber.uproot(handler);
}
public static void v(String message, Object... args) {
Timber.v(message, args);
}
public static void v(Throwable t, String message, Object... args) {
Timber.v(t, message, args);
}
public static void v(Throwable t) {
Timber.v(t);
}
public static void d(String message, Object... args) {
Timber.d(message, args);
}
public static void d(Throwable t, String message, Object... args) {
Timber.d(t, message, args);
}
public static void d(Throwable t) {
Timber.d(t);
}
public static void i(String message, Object... args) {
Timber.i(message, args);
}
public static void i(Throwable t, String message, Object... args) {
Timber.i(t, message, args);
}
public static void i(Throwable t) {
Timber.i(t);
}
public static void w(String message, Object... args) {
Timber.w(message, args);
}
public static void w(Throwable t, String message, Object... args) {
Timber.w(t, message, args);
}
public static void w(Throwable t) {
Timber.w(t);
}
public static void e(String message, Object... args) {
Timber.e(message, args);
}
public static void e(Throwable t, String message, Object... args) {
Timber.e(t, message, args);
}
public static void e(Throwable t) {
Timber.e(t);
}
}

View File

@@ -15,13 +15,12 @@
*/
package org.jitsi.meet.sdk.net;
import android.util.Log;
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 org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.net.UnknownHostException;
@@ -33,27 +32,29 @@ import java.net.UnknownHostException;
* [1]: https://tools.ietf.org/html/rfc6146
* [2]: https://tools.ietf.org/html/rfc6052
*/
@ReactModule(name = NAT64AddrInfoModule.NAME)
public class NAT64AddrInfoModule
extends ReactContextBaseJavaModule {
public final static String NAME = "NAT64AddrInfo";
/**
* The host for which the module wil try to resolve both IPv4 and IPv6
* addresses in order to figure out the NAT64 prefix.
*/
private final static String HOST = "ipv4only.arpa";
private final static String HOST = "nat64.jitsi.net";
/**
* How long is the {@link NAT64AddrInfo} instance valid.
*/
private final static long INFO_LIFETIME = 60 * 1000;
/**
* The name of this module.
*/
private final static String MODULE_NAME = "NAT64AddrInfo";
/**
* The {@code Log} tag {@code NAT64AddrInfoModule} is to log messages with.
*/
private final static String TAG = NAME;
private final static String TAG = MODULE_NAME;
/**
* The {@link NAT64AddrInfo} instance which holds NAT64 prefix/suffix.
@@ -97,7 +98,7 @@ public class NAT64AddrInfoModule
try {
info = NAT64AddrInfo.discover(host);
} catch (UnknownHostException e) {
JitsiMeetLogger.e(e, TAG + " NAT64AddrInfo.discover: " + host);
Log.e(TAG, "NAT64AddrInfo.discover: " + host, e);
}
infoTimestamp = System.currentTimeMillis();
}
@@ -107,7 +108,7 @@ public class NAT64AddrInfoModule
try {
result = info == null ? null : info.getIPv6Address(ipv4Address);
} catch (IllegalArgumentException exc) {
JitsiMeetLogger.e(exc, TAG + " Failed to get IPv6 address for: " + ipv4Address);
Log.e(TAG, "Failed to get IPv6 address for: " + ipv4Address, exc);
// We don't want to reject. It's not a big deal if there's no IPv6
// address resolved.
@@ -118,6 +119,6 @@ public class NAT64AddrInfoModule
@Override
public String getName() {
return NAME;
return MODULE_NAME;
}
}

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