diff --git a/android/app/build.gradle b/android/app/build.gradle index 3ac0fe3008..4b1dccf292 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,6 +1,7 @@ apply plugin: 'com.android.application' -boolean googleServicesEnabled = project.file('google-services.json').exists() +boolean googleServicesEnabled \ + = project.file('google-services.json').exists() && !rootProject.ext.libreBuild // Crashlytics integration is done as part of Firebase now, so it gets // automagically activated with google-services.json @@ -36,11 +37,24 @@ android { 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" + } + } } } @@ -58,7 +72,17 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) 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' + + 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 project(':sdk') @@ -73,13 +97,6 @@ dependencies { 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 { diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index 1773acf15c..2dfeaa5fc3 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -80,6 +80,7 @@ # 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 diff --git a/android/app/src/main/java/org/jitsi/meet/GoogleServicesHelper.java b/android/app/src/main/java/org/jitsi/meet/GoogleServicesHelper.java new file mode 100644 index 0000000000..61edd601aa --- /dev/null +++ b/android/app/src/main/java/org/jitsi/meet/GoogleServicesHelper.java @@ -0,0 +1,40 @@ +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()); + } + }); + } + } +} diff --git a/android/app/src/main/java/org/jitsi/meet/MainActivity.java b/android/app/src/main/java/org/jitsi/meet/MainActivity.java index 5b19751d7b..db22d99630 100644 --- a/android/app/src/main/java/org/jitsi/meet/MainActivity.java +++ b/android/app/src/main/java/org/jitsi/meet/MainActivity.java @@ -29,10 +29,7 @@ import org.jitsi.meet.sdk.JitsiMeet; import org.jitsi.meet.sdk.JitsiMeetActivity; import org.jitsi.meet.sdk.JitsiMeetConferenceOptions; -import com.crashlytics.android.Crashlytics; -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.Map; @@ -57,22 +54,16 @@ public class MainActivity extends JitsiMeetActivity { @Override protected boolean extraInitialize() { + Log.d(this.getClass().getSimpleName(), "LIBRE_BUILD="+BuildConfig.LIBRE_BUILD); + // Setup Crashlytics and Firebase Dynamic Links - if (BuildConfig.GOOGLE_SERVICES_ENABLED) { - Fabric.with(this, new Crashlytics()); - - FirebaseDynamicLinks.getInstance().getDynamicLink(getIntent()) - .addOnSuccessListener(this, pendingDynamicLinkData -> { - Uri dynamicLink = null; - - if (pendingDynamicLinkData != null) { - dynamicLink = pendingDynamicLinkData.getLink(); - } - - if (dynamicLink != null) { - join(dynamicLink.toString()); - } - }); + // 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. } // In Debug builds React needs permission to write over other apps in diff --git a/android/build.gradle b/android/build.gradle index 7f2ec26518..7320a1fff4 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -167,6 +167,9 @@ ext { // Glide excludeAppGlideModule = true glideVersion = "4.7.1" // keep in sync with react-native-fast-image + + // Libre build + libreBuild = (System.env.LIBRE_BUILD ?: "true").toBoolean() } // If Android SDK is not installed, accept its license so that it diff --git a/android/sdk/build.gradle b/android/sdk/build.gradle index 49b1a479ce..bca24d8192 100644 --- a/android/sdk/build.gradle +++ b/android/sdk/build.gradle @@ -10,10 +10,25 @@ android { } buildTypes { - debug {} + debug { + buildConfigField "boolean", "LIBRE_BUILD", "${rootProject.ext.libreBuild}" + } 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/" + } } } } @@ -24,19 +39,23 @@ dependencies { implementation "com.android.support:support-v4:${rootProject.ext.supportLibVersion}" implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}" - implementation 'com.amplitude:android-sdk:2.14.1' - implementation 'com.dropbox.core:dropbox-core-sdk:3.0.8' api 'com.facebook.react:react-native:+' + implementation 'com.dropbox.core:dropbox-core-sdk:3.0.8' + + 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: 'com.android.support' + } + } + implementation project(':react-native-background-timer') implementation project(':react-native-calendar-events') 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') diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/AppInfoModule.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/AppInfoModule.java index adf6bb1fd1..c39a0bd485 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/AppInfoModule.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/AppInfoModule.java @@ -75,6 +75,7 @@ class AppInfoModule constants.put( "version", packageInfo == null ? "" : packageInfo.versionName); + constants.put("LIBRE_BUILD", BuildConfig.LIBRE_BUILD); return constants; } diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/ReactInstanceManagerHolder.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/ReactInstanceManagerHolder.java index 0a722573df..4e3228e345 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/ReactInstanceManagerHolder.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/ReactInstanceManagerHolder.java @@ -21,6 +21,7 @@ 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; @@ -28,6 +29,7 @@ import com.facebook.react.common.LifecycleState; import com.facebook.react.devsupport.DevInternalSettings; import com.facebook.react.modules.core.DeviceEventManagerModule; +import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -48,7 +50,6 @@ class ReactInstanceManagerHolder { ReactApplicationContext reactContext) { List nativeModules = new ArrayList<>(Arrays.asList( - new AmplitudeModule(reactContext), new AndroidSettingsModule(reactContext), new AppInfoModule(reactContext), new AudioModeModule(reactContext), @@ -64,6 +65,14 @@ class ReactInstanceManagerHolder { nativeModules.add(new RNConnectionService(reactContext)); } + try { + Class amplitudeModuleClass = Class.forName("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; } @@ -128,31 +137,39 @@ class ReactInstanceManagerHolder { return; } + List packages + = new ArrayList<>(Arrays.asList( + new com.BV.LinearGradient.LinearGradientPackage(), + new com.calendarevents.CalendarEventsPackage(), + new com.corbt.keepawake.KCKeepAwakePackage(), + new com.dylanvann.fastimage.FastImageViewPackage(), + new com.facebook.react.shell.MainReactPackage(), + new com.oblador.vectoricons.VectorIconsPackage(), + new com.ocetnik.timer.BackgroundTimerPackage(), + new com.oney.WebRTCModule.WebRTCModulePackage(), + new com.rnimmersive.RNImmersivePackage(), + new com.zmxv.RNSound.RNSoundPackage(), + new ReactPackageAdapter() { + @Override + public List createNativeModules(ReactApplicationContext reactContext) { + return ReactInstanceManagerHolder.createNativeModules(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. + } + reactInstanceManager = ReactInstanceManager.builder() .setApplication(application) .setBundleAssetName("index.android.bundle") .setJSMainModulePath("index.android") - .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 createNativeModules( - ReactApplicationContext reactContext) { - return - ReactInstanceManagerHolder.createNativeModules( - reactContext); - } - }) + .addPackages(packages) .setUseDeveloperSupport(BuildConfig.DEBUG) .setInitialLifecycleState(LifecycleState.RESUMED) .build(); diff --git a/react/features/analytics/handlers/amplitude/Amplitude.native.js b/react/features/analytics/handlers/amplitude/Amplitude.native.js index 7124f1d154..01e16b73be 100644 --- a/react/features/analytics/handlers/amplitude/Amplitude.native.js +++ b/react/features/analytics/handlers/amplitude/Amplitude.native.js @@ -13,6 +13,11 @@ class Amplitude { * be used only for multi-project logging. */ constructor(instanceName) { + // It might not have been included in the build. + if (!AmplitudeNative) { + throw new Error('Amplitude analytics is not supported'); + } + this._instanceName = instanceName; } diff --git a/react/features/base/config/functions.js b/react/features/base/config/functions.any.js similarity index 99% rename from react/features/base/config/functions.js rename to react/features/base/config/functions.any.js index ee752b1f86..cb7e813fea 100644 --- a/react/features/base/config/functions.js +++ b/react/features/base/config/functions.any.js @@ -1,4 +1,4 @@ -/* @flow */ +// @flow import _ from 'lodash'; diff --git a/react/features/base/config/functions.native.js b/react/features/base/config/functions.native.js new file mode 100644 index 0000000000..9c31203b4c --- /dev/null +++ b/react/features/base/config/functions.native.js @@ -0,0 +1,21 @@ +// @flow + +import { NativeModules } from 'react-native'; + +export * from './functions.any'; + +/** + * Removes all analytics related options from the given configuration, in case of a libre build. + * + * @param {*} config - The configuration which needs to be cleaned up. + * @returns {void} + */ +export function _cleanupConfig(config: Object) { + if (NativeModules.AppInfo.LIBRE_BUILD) { + config.analytics.scriptURLs = []; + delete config.analytics.amplitudeAPPKey; + delete config.analytics.googleAnalyticsTrackingId; + delete config.callStatsID; + delete config.callStatsSecret; + } +} diff --git a/react/features/base/config/functions.web.js b/react/features/base/config/functions.web.js new file mode 100644 index 0000000000..59bba0486d --- /dev/null +++ b/react/features/base/config/functions.web.js @@ -0,0 +1,12 @@ +// @flow + +export * from './functions.any'; + +/** + * Removes all analytics related options from the given configuration, in case of a libre build. + * + * @param {*} config - The configuration which needs to be cleaned up. + * @returns {void} + */ +export function _cleanupConfig(config: Object) { // eslint-disable-line no-unused-vars +} diff --git a/react/features/base/config/reducer.js b/react/features/base/config/reducer.js index d15ae387b6..440423b631 100644 --- a/react/features/base/config/reducer.js +++ b/react/features/base/config/reducer.js @@ -5,6 +5,7 @@ import _ from 'lodash'; import { equals, ReducerRegistry, set } from '../redux'; import { CONFIG_WILL_LOAD, LOAD_CONFIG_ERROR, SET_CONFIG } from './actionTypes'; +import { _cleanupConfig } from './functions'; /** * The initial state of the feature base/config when executing in a @@ -29,6 +30,8 @@ const INITIAL_NON_RN_STATE = { * @type {Object} */ const INITIAL_RN_STATE = { + analytics: {}, + // FIXME The support for audio levels in lib-jitsi-meet polls the statistics // of WebRTC at a short interval multiple times a second. Unfortunately, // React Native is slow to fetch these statistics from the native WebRTC @@ -135,6 +138,8 @@ function _setConfig(state, { config }) { _getInitialState() ); + _cleanupConfig(newState); + return equals(state, newState) ? state : newState; } diff --git a/react/features/google-api/googleApi.native.js b/react/features/google-api/googleApi.native.js index afbb7b0eaf..38a99d14fe 100644 --- a/react/features/google-api/googleApi.native.js +++ b/react/features/google-api/googleApi.native.js @@ -1,8 +1,12 @@ // @flow -import { - GoogleSignin -} from 'react-native-google-signin'; +import { NativeModules } from 'react-native'; + +let GoogleSignin; + +if (NativeModules.RNGoogleSignin) { + GoogleSignin = require('react-native-google-signin').GoogleSignin; +} import { API_URL_BROADCAST_STREAMS, @@ -26,7 +30,9 @@ class GoogleApi { * @returns {void} */ configure(config: Object) { - GoogleSignin.configure(config); + if (GoogleSignin) { + GoogleSignin.configure(config); + } } /** @@ -58,6 +64,10 @@ class GoogleApi { * @returns {Promise<*>} */ hasPlayServices() { + if (!GoogleSignin) { + return Promise.reject(new Error('Google SignIn not supported')); + } + return GoogleSignin.hasPlayServices(); }