From b3f16926d45159c7c40025232980ece54953e9ad Mon Sep 17 00:00:00 2001 From: tmoldovan8x8 <62697631+tmoldovan8x8@users.noreply.github.com> Date: Fri, 8 May 2020 00:05:48 +0300 Subject: [PATCH] rn: add ability to disable crash reporting --- android/app/build.gradle | 11 ++- .../org/jitsi/meet/GoogleServicesHelper.java | 5 +- android/build.gradle | 2 + android/sdk/build.gradle | 3 + .../org/jitsi/meet/sdk/AppInfoModule.java | 1 + .../java/org/jitsi/meet/sdk/JitsiMeet.java | 11 ++- .../meet/sdk/ReactInstanceManagerHolder.java | 1 + android/settings.gradle | 4 +- ios/Podfile | 1 + ios/Podfile.lock | 15 ++-- ios/app/src/AppDelegate.m | 16 ++--- ios/app/src/FIRUtilities.m | 35 +-------- ios/sdk/sdk.xcodeproj/project.pbxproj | 8 +++ ios/sdk/src/AppInfo.m | 8 ++- ios/sdk/src/InfoPlistUtil.h | 23 ++++++ ios/sdk/src/InfoPlistUtil.m | 52 ++++++++++++++ ios/sdk/src/JitsiMeet.h | 3 + ios/sdk/src/JitsiMeet.m | 7 +- lang/main.json | 3 + package-lock.json | 5 ++ package.json | 1 + .../base/settings/functions.native.js | 16 +++++ react/features/base/settings/functions.web.js | 10 +++ react/features/base/settings/middleware.js | 16 ++++- react/features/base/settings/reducer.js | 1 + .../components/native/SettingsView.js | 71 ++++++++++++++++++- 26 files changed, 267 insertions(+), 62 deletions(-) create mode 100644 ios/sdk/src/InfoPlistUtil.h create mode 100644 ios/sdk/src/InfoPlistUtil.m diff --git a/android/app/build.gradle b/android/app/build.gradle index 9a9b91e0e..531d1e8af 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,8 +1,5 @@ apply plugin: 'com.android.application' -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 if (googleServicesEnabled) { @@ -13,7 +10,7 @@ if (googleServicesEnabled) { // 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) +def vcode = (int) (((new Date().getTime() / 1000) - 1546297200) / 10) android { compileSdkVersion rootProject.ext.compileSdkVersion @@ -143,8 +140,8 @@ gradle.projectsEvaluated { def targetName = variant.name.capitalize() def currentRunPackagerTask = tasks.create( - name: "run${targetName}ReactPackager", - type: Exec) { + name: "run${targetName}ReactPackager", + type: Exec) { group = "react" description = "Run the React packager." @@ -175,5 +172,5 @@ gradle.projectsEvaluated { } if (googleServicesEnabled) { - apply plugin: 'com.google.gms.google-services' + apply plugin: 'com.google.gms.google-services' } diff --git a/android/app/src/main/java/org/jitsi/meet/GoogleServicesHelper.java b/android/app/src/main/java/org/jitsi/meet/GoogleServicesHelper.java index 61edd601a..16932c24c 100644 --- a/android/app/src/main/java/org/jitsi/meet/GoogleServicesHelper.java +++ b/android/app/src/main/java/org/jitsi/meet/GoogleServicesHelper.java @@ -7,6 +7,7 @@ import com.crashlytics.android.Crashlytics; import com.google.firebase.dynamiclinks.FirebaseDynamicLinks; import io.fabric.sdk.android.Fabric; +import org.jitsi.meet.sdk.JitsiMeet; import org.jitsi.meet.sdk.JitsiMeetActivity; /** @@ -21,7 +22,9 @@ final class GoogleServicesHelper { if (BuildConfig.GOOGLE_SERVICES_ENABLED) { Log.d(activity.getClass().getSimpleName(), "Initializing Google Services"); - Fabric.with(activity, new Crashlytics()); + if (!JitsiMeet.isCrashReportingDisabled(activity)) { + Fabric.with(activity, new Crashlytics()); + } FirebaseDynamicLinks.getInstance().getDynamicLink(activity.getIntent()) .addOnSuccessListener(activity, pendingDynamicLinkData -> { diff --git a/android/build.gradle b/android/build.gradle index 8d5babf29..60758af5c 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -161,6 +161,8 @@ ext { // Libre build libreBuild = (System.env.LIBRE_BUILD ?: "false").toBoolean() + + googleServicesEnabled = project.file('app/google-services.json').exists() && !libreBuild } // Force the version of the Android build tools we have chosen on all diff --git a/android/sdk/build.gradle b/android/sdk/build.gradle index ed0971fb3..7ca071745 100644 --- a/android/sdk/build.gradle +++ b/android/sdk/build.gradle @@ -14,11 +14,13 @@ android { buildTypes { debug { buildConfigField "boolean", "LIBRE_BUILD", "${rootProject.ext.libreBuild}" + buildConfigField "boolean", "GOOGLE_SERVICES_ENABLED", "${rootProject.ext.googleServicesEnabled}" } release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' buildConfigField "boolean", "LIBRE_BUILD", "${rootProject.ext.libreBuild}" + buildConfigField "boolean", "GOOGLE_SERVICES_ENABLED", "${rootProject.ext.googleServicesEnabled}" } } @@ -70,6 +72,7 @@ dependencies { implementation project(':react-native-calendar-events') implementation project(':react-native-community-async-storage') implementation project(':react-native-community_netinfo') + implementation project(':react-native-default-preference') 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 c39a0bd48..61820308d 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 @@ -76,6 +76,7 @@ class AppInfoModule "version", packageInfo == null ? "" : packageInfo.versionName); constants.put("LIBRE_BUILD", BuildConfig.LIBRE_BUILD); + constants.put("GOOGLE_SERVICES_ENABLED", BuildConfig.GOOGLE_SERVICES_ENABLED); return constants; } diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeet.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeet.java index 03bf2f32e..82fd70587 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeet.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeet.java @@ -16,11 +16,14 @@ */ package org.jitsi.meet.sdk; +import android.content.Context; +import android.content.SharedPreferences; 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 @@ -72,4 +75,10 @@ public class JitsiMeet { reactInstanceManager.showDevOptionsDialog(); } } -} + + public static boolean isCrashReportingDisabled(Context context) { + SharedPreferences preferences = context.getSharedPreferences("jitsi-default-preferences", Context.MODE_PRIVATE); + String value = preferences.getString("isCrashReportingDisabled", ""); + return Boolean.parseBoolean(value); + } +} \ No newline at end of file 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 86f685006..3a8e6c245 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 @@ -190,6 +190,7 @@ class ReactInstanceManagerHolder { new com.corbt.keepawake.KCKeepAwakePackage(), new com.facebook.react.shell.MainReactPackage(), new com.horcrux.svg.SvgPackage(), + new com.kevinresol.react_native_default_preference.RNDefaultPreferencePackage(), new com.ocetnik.timer.BackgroundTimerPackage(), new com.reactnativecommunity.asyncstorage.AsyncStoragePackage(), new com.reactnativecommunity.netinfo.NetInfoPackage(), diff --git a/android/settings.gradle b/android/settings.gradle index 7a01488f9..8603d8eaf 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -9,6 +9,8 @@ include ':react-native-community-async-storage' project(':react-native-community-async-storage').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/async-storage/android') include ':react-native-community_netinfo' project(':react-native-community_netinfo').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/netinfo/android') +include ':react-native-default-preference' +project(':react-native-default-preference').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-default-preference/android') include ':react-native-google-signin' project(':react-native-google-signin').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/google-signin/android') include ':react-native-immersive' @@ -24,4 +26,4 @@ project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../ include ':react-native-webrtc' project(':react-native-webrtc').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webrtc/android') include ':react-native-webview' -project(':react-native-webview').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview/android') +project(':react-native-webview').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview/android') \ No newline at end of file diff --git a/ios/Podfile b/ios/Podfile index 410ea5c67..9483d129d 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -66,6 +66,7 @@ target 'JitsiMeet' do pod 'RNSound', :path => '../node_modules/react-native-sound' pod 'RNSVG', :path => '../node_modules/react-native-svg' pod 'RNWatch', :path => '../node_modules/react-native-watch-connectivity' + pod 'RNDefaultPreference', :path => '../node_modules/react-native-default-preference' # Native pod dependencies # diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a2f9da309..1afa315ed 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -353,6 +353,8 @@ PODS: - ReactCommon/turbomodule/core (= 0.61.5-jitsi.1) - RNCAsyncStorage (1.3.4): - React + - RNDefaultPreference (1.4.2): + - React - RNGoogleSignin (3.0.1): - GoogleSignIn (~> 5.0.0) - React @@ -409,6 +411,7 @@ DEPENDENCIES: - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) - ReactCommon/turbomodule (from `../node_modules/react-native/ReactCommon`) - "RNCAsyncStorage (from `../node_modules/@react-native-community/async-storage`)" + - RNDefaultPreference (from `../node_modules/react-native-default-preference`) - "RNGoogleSignin (from `../node_modules/@react-native-community/google-signin`)" - RNSound (from `../node_modules/react-native-sound`) - RNSVG (from `../node_modules/react-native-svg`) @@ -416,13 +419,11 @@ DEPENDENCIES: - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) SPEC REPOS: - https://github.com/CocoaPods/Specs.git: + trunk: - Amplitude-iOS + - AppAuth - boost-for-react-native - CocoaLumberjack - - ObjectiveDropboxOfficial - trunk: - - AppAuth - Crashlytics - Fabric - Firebase @@ -442,6 +443,7 @@ SPEC REPOS: - GTMAppAuth - GTMSessionFetcher - nanopb + - ObjectiveDropboxOfficial - PromisesObjC EXTERNAL SOURCES: @@ -509,6 +511,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon" RNCAsyncStorage: :path: "../node_modules/@react-native-community/async-storage" + RNDefaultPreference: + :path: "../node_modules/react-native-default-preference" RNGoogleSignin: :path: "../node_modules/@react-native-community/google-signin" RNSound: @@ -578,12 +582,13 @@ SPEC CHECKSUMS: React-RCTVibration: a1bcfcdc0b5a73a1b0829a34cee22bd0e95bacba ReactCommon: 675681aba4fecff5acbc0e440530cc422103c610 RNCAsyncStorage: 8e31405a9f12fbf42c2bb330e4560bfd79c18323 + RNDefaultPreference: 56a405ce61033ac77b95004dccd7ac54c2eb50d1 RNGoogleSignin: 39336070b35fc4cea6a98cf111e00480317be0ae RNSound: c980916b596cc15c8dcd2f6ecd3b13c4881dbe20 RNSVG: aac12785382e8fd4f28d072fe640612e34914631 RNWatch: 09738b339eceb66e4d80a2371633ca5fb380fa42 Yoga: 7b4209fda2441f99d54dd6cf4c82b094409bb68f -PODFILE CHECKSUM: f615794fb9184757b00cd16e534824ba6ee2fc98 +PODFILE CHECKSUM: 082858daebbe170e7a490de433e7f2a99e0c3701 COCOAPODS: 1.9.1 diff --git a/ios/app/src/AppDelegate.m b/ios/app/src/AppDelegate.m index 1ca70f321..237466c5c 100644 --- a/ios/app/src/AppDelegate.m +++ b/ios/app/src/AppDelegate.m @@ -24,19 +24,10 @@ @import Firebase; @import JitsiMeet; - @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - - // Initialize Crashlytics and Firebase if a valid GoogleService-Info.plist file was provided. - if ([FIRUtilities appContainsRealServiceInfoPlist]) { - NSLog(@"Enablign Crashlytics and Firebase"); - [FIRApp configure]; - [Fabric with:@[[Crashlytics class]]]; - } - JitsiMeet *jitsiMeet = [JitsiMeet sharedInstance]; jitsiMeet.conferenceActivityType = JitsiMeetConferenceActivityType; @@ -54,6 +45,13 @@ #endif }]; + // Initialize Crashlytics and Firebase if a valid GoogleService-Info.plist file was provided. + if ([FIRUtilities appContainsRealServiceInfoPlist] && ![jitsiMeet isCrashReportingDisabled]) { + NSLog(@"Enabling Crashlytics and Firebase"); + [FIRApp configure]; + [Fabric with:@[[Crashlytics class]]]; + } + [jitsiMeet application:application didFinishLaunchingWithOptions:launchOptions]; return YES; diff --git a/ios/app/src/FIRUtilities.m b/ios/app/src/FIRUtilities.m index a8f5b4184..535dddd7d 100644 --- a/ios/app/src/FIRUtilities.m +++ b/ios/app/src/FIRUtilities.m @@ -16,12 +16,7 @@ #import "FIRUtilities.h" -// Plist file name. -NSString *const kGoogleServiceInfoFileName = @"GoogleService-Info"; -// Plist file type. -NSString *const kGoogleServiceInfoFileType = @"plist"; -NSString *const kGoogleAppIDPlistKey = @"GOOGLE_APP_ID"; - +@import JitsiMeet; @implementation FIRUtilities @@ -30,37 +25,11 @@ NSString *const kGoogleAppIDPlistKey = @"GOOGLE_APP_ID"; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSBundle *bundle = [NSBundle mainBundle]; - containsRealServiceInfoPlist = [self containsRealServiceInfoPlistInBundle:bundle]; + containsRealServiceInfoPlist = [InfoPlistUtil containsRealServiceInfoPlistInBundle:bundle]; }); return containsRealServiceInfoPlist; } -+ (BOOL)containsRealServiceInfoPlistInBundle:(NSBundle *)bundle { - NSString *bundlePath = bundle.bundlePath; - if (!bundlePath.length) { - return NO; - } - - NSString *plistFilePath = [bundle pathForResource:kGoogleServiceInfoFileName - ofType:kGoogleServiceInfoFileType]; - if (!plistFilePath.length) { - return NO; - } - - NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:plistFilePath]; - if (!plist) { - return NO; - } - - // Perform a very naive validation by checking to see if the plist has the dummy google app id - NSString *googleAppID = plist[kGoogleAppIDPlistKey]; - if (!googleAppID.length) { - return NO; - } - - return YES; -} - + (NSURL *)extractURL: (FIRDynamicLink*)dynamicLink { NSURL *url = nil; if (dynamicLink != nil) { diff --git a/ios/sdk/sdk.xcodeproj/project.pbxproj b/ios/sdk/sdk.xcodeproj/project.pbxproj index 5cc406f47..c899d49e7 100644 --- a/ios/sdk/sdk.xcodeproj/project.pbxproj +++ b/ios/sdk/sdk.xcodeproj/project.pbxproj @@ -42,6 +42,8 @@ C69EFA0E209A0F660027712B /* JMCallKitListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69EFA0B209A0F660027712B /* JMCallKitListener.swift */; }; C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425E204EF76800E062DD /* DragGestureController.swift */; }; C6CC49AF207412CF000DFA42 /* PiPViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */; }; + C8AFD27F2462C613000293D2 /* InfoPlistUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = C8AFD27D2462C613000293D2 /* InfoPlistUtil.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C8AFD2802462C613000293D2 /* InfoPlistUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = C8AFD27E2462C613000293D2 /* InfoPlistUtil.m */; }; DE438CDA2350934700DD541D /* JavaScriptSandbox.m in Sources */ = {isa = PBXBuildFile; fileRef = DE438CD82350934700DD541D /* JavaScriptSandbox.m */; }; DE65AACA2317FFCD00290BEC /* LogUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = DE65AAC92317FFCD00290BEC /* LogUtils.h */; }; DE65AACC2318028300290BEC /* JitsiMeetBaseLogHandler+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DE65AACB2318028300290BEC /* JitsiMeetBaseLogHandler+Private.h */; }; @@ -105,6 +107,8 @@ C6A3425E204EF76800E062DD /* DragGestureController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DragGestureController.swift; sourceTree = ""; }; C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiPViewCoordinator.swift; sourceTree = ""; }; C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetView+Private.h"; sourceTree = ""; }; + C8AFD27D2462C613000293D2 /* InfoPlistUtil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InfoPlistUtil.h; sourceTree = ""; }; + C8AFD27E2462C613000293D2 /* InfoPlistUtil.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InfoPlistUtil.m; sourceTree = ""; }; DE438CD82350934700DD541D /* JavaScriptSandbox.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JavaScriptSandbox.m; sourceTree = ""; }; DE65AAC92317FFCD00290BEC /* LogUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LogUtils.h; sourceTree = ""; }; DE65AACB2318028300290BEC /* JitsiMeetBaseLogHandler+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetBaseLogHandler+Private.h"; sourceTree = ""; }; @@ -223,6 +227,8 @@ DEFE535521FB2E8300011A3A /* ReactUtils.m */, 0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */, 0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */, + C8AFD27D2462C613000293D2 /* InfoPlistUtil.h */, + C8AFD27E2462C613000293D2 /* InfoPlistUtil.m */, ); path = src; sourceTree = ""; @@ -303,6 +309,7 @@ DE81A2D42316AC4D00AE1940 /* JitsiMeetLogger.h in Headers */, DE65AACA2317FFCD00290BEC /* LogUtils.h in Headers */, DEAD3226220C497000E93636 /* JitsiMeetConferenceOptions.h in Headers */, + C8AFD27F2462C613000293D2 /* InfoPlistUtil.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -482,6 +489,7 @@ 0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */, 0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */, 0BCA49601EC4B6C600B793EE /* POSIX.m in Sources */, + C8AFD2802462C613000293D2 /* InfoPlistUtil.m in Sources */, C6CC49AF207412CF000DFA42 /* PiPViewCoordinator.swift in Sources */, DEFC743F21B178FA00E4DD96 /* LocaleDetector.m in Sources */, 0BCA495F1EC4B6C600B793EE /* AudioMode.m in Sources */, diff --git a/ios/sdk/src/AppInfo.m b/ios/sdk/src/AppInfo.m index 37bb69e36..d74272917 100644 --- a/ios/sdk/src/AppInfo.m +++ b/ios/sdk/src/AppInfo.m @@ -19,6 +19,8 @@ #import #import +#import "InfoPlistUtil.h" + @interface AppInfo : NSObject @end @@ -67,13 +69,15 @@ RCT_EXPORT_MODULE(); buildNumber = @""; } + BOOL isGoogleServiceEnabled = [InfoPlistUtil containsRealServiceInfoPlistInBundle:[NSBundle mainBundle]]; + return @{ @"calendarEnabled": [NSNumber numberWithBool:calendarEnabled], @"buildNumber": buildNumber, @"name": name, @"sdkBundlePath": sdkBundlePath, - @"version": version + @"version": version, + @"GOOGLE_SERVICES_ENABLED": [NSNumber numberWithBool:isGoogleServiceEnabled] }; }; - @end diff --git a/ios/sdk/src/InfoPlistUtil.h b/ios/sdk/src/InfoPlistUtil.h new file mode 100644 index 000000000..f7d5bb8f9 --- /dev/null +++ b/ios/sdk/src/InfoPlistUtil.h @@ -0,0 +1,23 @@ +/* +* 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. +*/ + +#import + +@interface InfoPlistUtil : NSObject + ++ (BOOL)containsRealServiceInfoPlistInBundle:(NSBundle *)bundle; + +@end diff --git a/ios/sdk/src/InfoPlistUtil.m b/ios/sdk/src/InfoPlistUtil.m new file mode 100644 index 000000000..1c98522db --- /dev/null +++ b/ios/sdk/src/InfoPlistUtil.m @@ -0,0 +1,52 @@ +/* +* 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. +*/ + +#import "InfoPlistUtil.h" + +// Plist file name. +NSString *const kGoogleServiceInfoFileName = @"GoogleService-Info"; +// Plist file type. +NSString *const kGoogleServiceInfoFileType = @"plist"; +NSString *const kGoogleAppIDPlistKey = @"GOOGLE_APP_ID"; + +@implementation InfoPlistUtil + ++ (BOOL)containsRealServiceInfoPlistInBundle:(NSBundle *)bundle { + NSString *bundlePath = bundle.bundlePath; + if (!bundlePath.length) { + return NO; + } + + NSString *plistFilePath = [bundle pathForResource:kGoogleServiceInfoFileName + ofType:kGoogleServiceInfoFileType]; + if (!plistFilePath.length) { + return NO; + } + + NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:plistFilePath]; + if (!plist) { + return NO; + } + + // Perform a very naive validation by checking to see if the plist has the dummy google app id + NSString *googleAppID = plist[kGoogleAppIDPlistKey]; + if (!googleAppID.length) { + return NO; + } + + return YES; +} +@end diff --git a/ios/sdk/src/JitsiMeet.h b/ios/sdk/src/JitsiMeet.h index a268222de..8db09854d 100644 --- a/ios/sdk/src/JitsiMeet.h +++ b/ios/sdk/src/JitsiMeet.h @@ -20,6 +20,7 @@ #import #import #import +#import @interface JitsiMeet : NSObject @@ -64,4 +65,6 @@ - (JitsiMeetConferenceOptions *_Nonnull)getInitialConferenceOptions; +- (BOOL)isCrashReportingDisabled; + @end diff --git a/ios/sdk/src/JitsiMeet.m b/ios/sdk/src/JitsiMeet.m index 2f1639312..5272a9392 100644 --- a/ios/sdk/src/JitsiMeet.m +++ b/ios/sdk/src/JitsiMeet.m @@ -107,7 +107,7 @@ JitsiMeetConferenceOptions *conferenceOptions = [JitsiMeetConferenceOptions fromBuilder:^(JitsiMeetConferenceOptionsBuilder *builder) { builder.room = [url absoluteString]; }]; - + return [JitsiMeetView setPropsInViews:[conferenceOptions asProps]]; } @@ -132,6 +132,11 @@ return nil; } +- (BOOL)isCrashReportingDisabled { + NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"jitsi-default-preferences"]; + return [userDefaults stringForKey:@"isCrashReportingDisabled"]; +} + - (JitsiMeetConferenceOptions *)optionsFromUserActivity:(NSUserActivity *)userActivity { NSString *activityType = userActivity.activityType; diff --git a/lang/main.json b/lang/main.json index aae7eb7a8..267340cfb 100644 --- a/lang/main.json +++ b/lang/main.json @@ -582,12 +582,15 @@ "settingsView": { "advanced": "Advanced", "alertOk": "OK", + "alertCancel": "Cancel", "alertTitle": "Warning", "alertURLText": "The entered server URL is invalid", "buildInfoSection": "Build Information", "conferenceSection": "Conference", "disableCallIntegration": "Disable native call integration", "disableP2P": "Disable Peer-To-Peer mode", + "disableCrashReporting": "Disable crash reporting", + "disableCrashReportingWarning": "Are you sure you want to disable crash reporting? The setting will be applied after you restart the app.", "displayName": "Display name", "email": "Email", "header": "Settings", diff --git a/package-lock.json b/package-lock.json index 644259f75..69fc95a74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14827,6 +14827,11 @@ } } }, + "react-native-default-preference": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/react-native-default-preference/-/react-native-default-preference-1.4.2.tgz", + "integrity": "sha512-kNhBLv8s6kO2gJJFEKM7qew7oRvJnygjgG1CU2ZEY6SlG5qsRX8z1Ms7z1Oo/XB7fVfyXrAoZDGhIvy+uiByrg==" + }, "react-native-immersive": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/react-native-immersive/-/react-native-immersive-2.0.0.tgz", diff --git a/package.json b/package.json index 5027f8973..37d0db900 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "react-native-calendar-events": "github:jitsi/react-native-calendar-events#902e6e92d6bae450a6052f76ba4d02f977ffd8f2", "react-native-callstats": "3.61.0", "react-native-collapsible": "1.5.1", + "react-native-default-preference": "1.4.2", "react-native-immersive": "2.0.0", "react-native-keep-awake": "4.0.0", "react-native-linear-gradient": "2.5.6", diff --git a/react/features/base/settings/functions.native.js b/react/features/base/settings/functions.native.js index 5337417b9..1d5f4b00b 100644 --- a/react/features/base/settings/functions.native.js +++ b/react/features/base/settings/functions.native.js @@ -2,6 +2,8 @@ import { NativeModules } from 'react-native'; +import DefaultPreference from 'react-native-default-preference'; + export * from './functions.any'; const { AudioMode } = NativeModules; @@ -19,3 +21,17 @@ export function handleCallIntegrationChange(disabled: boolean) { AudioMode.setUseConnectionService(!disabled); } } + +/** + * Handles changes to the `disableCrashReporting` setting. + * Stores the value into platform specific default preference file, so at app + * start-up time it is retrieved on the native side and the crash reporting + * is enabled/disabled. + * + * @param {boolean} disabled - Whether crash reporting is disabled or not. + * @returns {void} + */ +export function handleCrashReportingChange(disabled: boolean) { + DefaultPreference.setName('jitsi-default-preferences').then( + DefaultPreference.set('isCrashReportingDisabled', disabled.toString())); +} diff --git a/react/features/base/settings/functions.web.js b/react/features/base/settings/functions.web.js index 59f8cebe7..f646ae4d5 100644 --- a/react/features/base/settings/functions.web.js +++ b/react/features/base/settings/functions.web.js @@ -41,3 +41,13 @@ export function getCurrentOutputDeviceId(state: Object) { */ export function handleCallIntegrationChange(disabled: boolean) { // eslint-disable-line no-unused-vars } + +/** + * Handles changes to the `disableCrashReporting` setting. + * Noop on web. + * + * @param {boolean} disabled - Whether crash reporting is disabled or not. + * @returns {void} + */ +export function handleCrashReportingChange(disabled: boolean) { // eslint-disable-line no-unused-vars +} diff --git a/react/features/base/settings/middleware.js b/react/features/base/settings/middleware.js index c38141a9c..badf54941 100644 --- a/react/features/base/settings/middleware.js +++ b/react/features/base/settings/middleware.js @@ -9,7 +9,7 @@ import { getLocalParticipant, participantUpdated } from '../participants'; import { MiddlewareRegistry } from '../redux'; import { SETTINGS_UPDATED } from './actionTypes'; -import { handleCallIntegrationChange } from './functions'; +import { handleCallIntegrationChange, handleCrashReportingChange } from './functions'; /** * The middleware of the feature base/settings. Distributes changes to the state @@ -30,6 +30,7 @@ MiddlewareRegistry.register(store => next => action => { _maybeHandleCallIntegrationChange(action); _maybeSetAudioOnly(store, action); _updateLocalParticipant(store, action); + _maybeCrashReportingChange(action); break; case SET_LOCATION_URL: _updateLocalParticipantFromUrl(store); @@ -84,6 +85,19 @@ function _maybeHandleCallIntegrationChange({ settings: { disableCallIntegration } } +/** + * Handles a change in the `disableCrashReporting` setting. + * + * @param {Object} action - The redux action. + * @private + * @returns {void} + */ +function _maybeCrashReportingChange({ settings: { disableCrashReporting } }) { + if (typeof disableCrashReporting === 'boolean') { + handleCrashReportingChange(disableCrashReporting); + } +} + /** * Updates {@code startAudioOnly} flag if it's updated in the settings. * diff --git a/react/features/base/settings/reducer.js b/react/features/base/settings/reducer.js index ce5cfdf9e..f1bfed5b2 100644 --- a/react/features/base/settings/reducer.js +++ b/react/features/base/settings/reducer.js @@ -24,6 +24,7 @@ const DEFAULT_STATE = { avatarURL: undefined, cameraDeviceId: undefined, disableCallIntegration: undefined, + disableCrashReporting: undefined, disableP2P: undefined, displayName: undefined, email: undefined, diff --git a/react/features/settings/components/native/SettingsView.js b/react/features/settings/components/native/SettingsView.js index f7344a4e2..95b30a091 100644 --- a/react/features/settings/components/native/SettingsView.js +++ b/react/features/settings/components/native/SettingsView.js @@ -36,6 +36,11 @@ type State = { */ disableP2P: boolean, + /** + * State variable for the disable crash reporting switch. + */ + disableCrashReporting: boolean, + /** * State variable for the display name field. */ @@ -84,6 +89,7 @@ class SettingsView extends AbstractSettingsView { super(props); const { disableCallIntegration, + disableCrashReporting, disableP2P, displayName, email, @@ -94,6 +100,7 @@ class SettingsView extends AbstractSettingsView { this.state = { disableCallIntegration, + disableCrashReporting, disableP2P, displayName, email, @@ -107,6 +114,7 @@ class SettingsView extends AbstractSettingsView { this._onBlurServerURL = this._onBlurServerURL.bind(this); this._onClose = this._onClose.bind(this); this._onDisableCallIntegration = this._onDisableCallIntegration.bind(this); + this._onDisableCrashReporting = this._onDisableCrashReporting.bind(this); this._onDisableP2P = this._onDisableP2P.bind(this); this._onShowAdvanced = this._onShowAdvanced.bind(this); this._setURLFieldReference = this._setURLFieldReference.bind(this); @@ -285,6 +293,24 @@ class SettingsView extends AbstractSettingsView { }); } + _onDisableCrashReporting: (boolean) => void; + + /** + * Handles the disable crash reporting change event. + * + * @param {boolean} disableCrashReporting - The new value + * option. + * @private + * @returns {void} + */ + _onDisableCrashReporting(disableCrashReporting) { + if (disableCrashReporting) { + this._showCrashReportingDisableAlert(); + } else { + this._disableCrashReporting(disableCrashReporting); + } + } + _onClose: () => void; /** @@ -367,7 +393,7 @@ class SettingsView extends AbstractSettingsView { * @returns {React$Element} */ _renderAdvancedSettings() { - const { disableCallIntegration, disableP2P, showAdvanced } = this.state; + const { disableCallIntegration, disableP2P, disableCrashReporting, showAdvanced } = this.state; if (!showAdvanced) { return ( @@ -397,6 +423,15 @@ class SettingsView extends AbstractSettingsView { onValueChange = { this._onDisableP2P } value = { disableP2P } /> + {AppInfo.GOOGLE_SERVICES_ENABLED && ( + + + + )} ); } @@ -436,7 +471,41 @@ class SettingsView extends AbstractSettingsView { ); } + /** + * Shows an alert warning the user about disabling crash reporting. + * + * @returns {void} + */ + _showCrashReportingDisableAlert() { + const { t } = this.props; + + Alert.alert( + t('settingsView.alertTitle'), + t('settingsView.disableCrashReportingWarning'), + [ + { + onPress: () => this._disableCrashReporting(true), + text: t('settingsView.alertOk') + }, + { + text: t('settingsView.alertCancel') + } + ] + ); + } + _updateSettings: (Object) => void; + + /** + * Updates the settings and sets state for disableCrashReporting. + * + * @param {boolean} disableCrashReporting - Whether crash reporting is disabled or not. + * @returns {void} + */ + _disableCrashReporting(disableCrashReporting) { + this._updateSettings({ disableCrashReporting }); + this.setState({ disableCrashReporting }); + } } /**