diff --git a/native/android/app/CMakeLists.txt b/native/android/app/CMakeLists.txt index 9fbfc6156..e63163feb 100644 --- a/native/android/app/CMakeLists.txt +++ b/native/android/app/CMakeLists.txt @@ -1,245 +1,246 @@ # For more information about using CMake with Android Studio, read the # documentation: https://d.android.com/studio/projects/add-native-code.html project(comm CXX C) set(CMAKE_CXX_STANDARD 17) # C0103 is a naming convention, but the variable names which need to be set # are determined by the upstream project # cmake-lint: disable=C0103 # Disable line length as some paths are hard to reduce without becoming cryptic # cmake-lint: disable=C0301 # Sets the minimum version of CMake required to build the native library. cmake_minimum_required(VERSION 3.18) # Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. # Gradle automatically packages shared libraries with your APK. set(PACKAGE_NAME "comm_jni_module") find_library(log-lib log) find_package(fbjni REQUIRED CONFIG) set(BUILD_TESTING OFF) set(HAVE_SYMBOLIZE OFF) set(WITH_GTEST OFF CACHE BOOL "Use googletest" FORCE) set(WITH_GFLAGS OFF CACHE BOOL "Use gflags" FORCE) # General set(_third_party_dir ${CMAKE_CURRENT_SOURCE_DIR}/build/third-party-ndk) set(_android_build_dir build/${CMAKE_ANDROID_ARCH_ABI}) include(FetchContent) if(CMAKE_ANDROID_ARCH_ABI STREQUAL arm64-v8a) set(Rust_CARGO_TARGET aarch64-linux-android) elseif(CMAKE_ANDROID_ARCH_ABI STREQUAL x86_64) set(Rust_CARGO_TARGET x86_64-linux-android) elseif(CMAKE_ANDROID_ARCH_ABI STREQUAL armeabi-v7a) set(Rust_CARGO_TARGET armv7-linux-androideabi) endif() string(TOLOWER ${CMAKE_HOST_SYSTEM_NAME} CMAKE_HOST_SYSTEM_NAME_LOWER) set(_toolchain_path "$ENV{ANDROID_HOME}/ndk/${NDK_VERSION}/toolchains/llvm/prebuilt/${CMAKE_HOST_SYSTEM_NAME_LOWER}-x86_64/bin" ) if(EXISTS "${_toolchain_path}/${Rust_CARGO_TARGET}-ar") set(AR "${_toolchain_path}/${Rust_CARGO_TARGET}-ar") else() set(AR "${_toolchain_path}/llvm-ar") endif() FetchContent_Declare( Corrosion GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git GIT_TAG v0.2.1 ) FetchContent_MakeAvailable(Corrosion) include(../../../shared/cmake/corrosion-cxx.cmake) add_library_rust(PATH ../../native_rust_library NAMESPACE comm) # We're updating parameters below for Cmake's find_OpenSSL() function set(OPENSSL_ROOT_DIR "${_third_party_dir}/openssl/openssl-${OPENSSL_VERSION}/${_android_build_dir}" ) list(APPEND CMAKE_FIND_ROOT_PATH "${OPENSSL_ROOT_DIR}") # Override HAVE_EXECINFO_H in glog's CMakeLists.txt if( CMAKE_ANDROID_ARCH_ABI STREQUAL arm64-v8a OR CMAKE_ANDROID_ARCH_ABI STREQUAL armeabi-v7a ) set(HAVE_EXECINFO_H OFF CACHE BOOL "Whether platform has execinfo.h") endif() add_subdirectory(${_third_party_dir}/glog/glog-${GLOG_VERSION}/) file(GLOB LIBRN_DIR "${REACT_NATIVE_SO_DIR}/${ANDROID_ABI}") if (NOT LIBRN_DIR) # If /${ANDROID_ABI} dir not found, then ${REACT_NATIVE_SO_DIR} is probably: # ReactAndroid/build/react-ndk/exported file(GLOB LIBRN_DIR "${REACT_NATIVE_SO_DIR}") endif () include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/folly-target.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/openssl-target.cmake) add_subdirectory(../../node_modules/olm ./build) set(_node_modules_dir ${CMAKE_CURRENT_SOURCE_DIR}/../../node_modules) set(_react_native_dir ${_node_modules_dir}/react-native) add_subdirectory(../../cpp/CommonCpp/ ${CMAKE_CURRENT_BINARY_DIR}/build/CommonCpp EXCLUDE_FROM_ALL ) file(GLOB SQLCIPHER "${_node_modules_dir}/@commapp/sqlcipher-amalgamation/src/*.c" ) # Add files which aren't a part of comm-tools list(APPEND ANDROID_NATIVE_CODE "./src/cpp/CommSecureStore.cpp" "./src/cpp/DatabaseInitializerJNIHelper.cpp" "./src/cpp/GlobalDBSingleton.cpp" "./src/cpp/Logger.cpp" "./src/cpp/MessageOperationsUtilitiesJNIHelper.cpp" "./src/cpp/PlatformSpecificTools.cpp" "./src/cpp/TerminateApp.cpp" "./src/cpp/ThreadOperationsJNIHelper.cpp" "./src/cpp/jsiInstaller.cpp" "./src/cpp/NotificationsCryptoModuleJNIHelper.cpp" "./src/cpp/StaffUtilsJNIHelper.cpp" "./src/cpp/AESCrypto.cpp" "./src/cpp/CommServicesAuthMetadataEmitter.cpp" + "./src/cpp/CommMMKV.cpp" ) list(APPEND GENERATED_NATIVE_CODE "../../cpp/CommonCpp/_generated/commJSI-generated.cpp" "../../cpp/CommonCpp/_generated/utilsJSI-generated.cpp" "../../cpp/CommonCpp/_generated/rustJSI-generated.cpp" ) list(APPEND RUST_NATIVE_CODE "../../native_rust_library/RustCallback.cpp" "../../native_rust_library/RustAESCrypto.cpp" "../../native_rust_library/RustCSAMetadataEmitter.cpp" "../../native_rust_library/RustSecureStore.cpp" "../../native_rust_library/RustBackupExecutor.cpp" ) file(GLOB CRYPTO_NATIVE_CODE "../../cpp/CommonCpp/CryptoTools/*.cpp") file(GLOB DB_NATIVE_CODE "../../cpp/CommonCpp/DatabaseManagers/*.cpp") file(GLOB DB_ENTITIES_NATIVE_CODE "../../cpp/CommonCpp/DatabaseManagers/entities/*.cpp") file(GLOB_RECURSE MODULE_NATIVE_CODE "../../cpp/CommonCpp/NativeModules/**/*.cpp") file(GLOB MODULE_ROOT_NATIVE_CODE "../../cpp/CommonCpp/NativeModules/*.cpp") file(GLOB NOTIFICATIONS_NATIVE_CODE "../../cpp/CommonCpp/Notifications/**/*.cpp") add_library( # Sets the name of the library ${PACKAGE_NAME} # Sets the library as a shared library SHARED # React dependencies ${_react_native_dir}/ReactCommon/jsi/jsi/jsi.cpp ${_react_native_dir}/ReactCommon/jsi/jsi/JSIDynamic.cpp ${_react_native_dir}/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/CallInvokerHolder.cpp ${_react_native_dir}/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.cpp ${_react_native_dir}/ReactCommon/react/bridging/LongLivedObject.cpp ${_react_native_dir}/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleUtils.cpp # Third party dependencies ${SQLCIPHER} # comm code ${ANDROID_NATIVE_CODE} ${GENERATED_NATIVE_CODE} ${CRYPTO_NATIVE_CODE} ${DB_NATIVE_CODE} ${DB_ENTITIES_NATIVE_CODE} ${MODULE_NATIVE_CODE} ${MODULE_ROOT_NATIVE_CODE} ${TOOLS_NATIVE_CODE} ${NOTIFICATIONS_NATIVE_CODE} ${RUST_NATIVE_CODE} ) set(BUILD_DIR ${CMAKE_SOURCE_DIR}/build) target_include_directories( ${PACKAGE_NAME} PRIVATE # React Native ${_react_native_dir}/React ${_react_native_dir}/React/Base ${_react_native_dir}/ReactCommon ${_react_native_dir}/ReactCommon/jsi ${_react_native_dir}/ReactCommon/callinvoker ${_react_native_dir}/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon # SQLCipher amalgamation ${_node_modules_dir}/@commapp/sqlcipher-amalgamation/src # symlinked React Native headers ../headers # comm android specific code ./src/cpp # comm native mutual code ../../cpp/CommonCpp/ ../../cpp/CommonCpp/NativeModules ../../cpp/CommonCpp/NativeModules/InternalModules ../../cpp/CommonCpp/NativeModules/PersistentStorageUtilities ../../cpp/CommonCpp/NativeModules/PersistentStorageUtilities/BackupOperationsUtilities ../../cpp/CommonCpp/NativeModules/PersistentStorageUtilities/DataStores ../../cpp/CommonCpp/NativeModules/PersistentStorageUtilities/ThreadOperationsUtilities ../../cpp/CommonCpp/NativeModules/PersistentStorageUtilities/MessageOperationsUtilities ../../cpp/CommonCpp/NativeModules/PersistentStorageUtilities/MessageOperationsUtilities/MessageSpecs ../../cpp/CommonCpp/DatabaseManagers ../../cpp/CommonCpp/Notifications ../../cpp/CommonCpp/Notifications/BackgroundDataStorage # native rust library ${native_rust_library_include_dir} ) add_definitions( # SQLCipher -DSQLITE_THREADSAFE=0 -DSQLITE_HAS_CODEC -DSQLITE_TEMP_STORE=2 -DSQLCIPHER_CRYPTO_OPENSSL -DSQLITE_ENABLE_SESSION -DSQLITE_ENABLE_PREUPDATE_HOOK ) target_link_libraries( ${PACKAGE_NAME} fbjni::fbjni android ${log-lib} Folly::folly glog::glog olm openssl-crypto openssl-ssl comm::native_rust_library comm-tools ) # add a dummy library which is required by CallInvokerHolderImpl.java add_library( turbomodulejsijni # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). ./src/cpp/dummy.cpp ) diff --git a/native/android/app/build.gradle b/native/android/app/build.gradle index 64c2fc76f..8804dcead 100644 --- a/native/android/app/build.gradle +++ b/native/android/app/build.gradle @@ -1,747 +1,748 @@ apply plugin: "com.android.application" import com.android.build.OutputFile import de.undercouch.gradle.tasks.download.Download import app.comm.gradle.tasks.GitModules /** * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets * and bundleReleaseJsAndAssets). * These basically call `react-native bundle` with the correct arguments during the Android build * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the * bundle directly from the development server. Below you can see all the possible configurations * and their defaults. If you decide to add a configuration block, make sure to add it before the * `apply from: "../../node_modules/react-native/react.gradle"` line. * * project.ext.react = [ * // the name of the generated asset file containing your JS bundle * bundleAssetName: "index.android.bundle", * * // the entry file for bundle generation. If none specified and * // "index.android.js" exists, it will be used. Otherwise "index.js" is * // default. Can be overridden with ENTRY_FILE environment variable. * entryFile: "index.android.js", * * // https://reactnative.dev/docs/performance#enable-the-ram-format * bundleCommand: "ram-bundle", * * // whether to bundle JS and assets in debug mode * bundleInDebug: false, * * // whether to bundle JS and assets in release mode * bundleInRelease: true, * * // whether to bundle JS and assets in another build variant (if configured). * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants * // The configuration property can be in the following formats * // 'bundleIn${productFlavor}${buildType}' * // 'bundleIn${buildType}' * // bundleInFreeDebug: true, * // bundleInPaidRelease: true, * // bundleInBeta: true, * * // whether to disable dev mode in custom build variants (by default only disabled in release) * // for example: to disable dev mode in the staging build type (if configured) * devDisabledInStaging: true, * // The configuration property can be in the following formats * // 'devDisabledIn${productFlavor}${buildType}' * // 'devDisabledIn${buildType}' * * // the root of your project, i.e. where "package.json" lives * root: "../../", * * // where to put the JS bundle asset in debug mode * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", * * // where to put the JS bundle asset in release mode * jsBundleDirRelease: "$buildDir/intermediates/assets/release", * * // where to put drawable resources / React Native assets, e.g. the ones you use via * // require('./image.png')), in debug mode * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", * * // where to put drawable resources / React Native assets, e.g. the ones you use via * // require('./image.png')), in release mode * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", * * // by default the gradle tasks are skipped if none of the JS files or assets change; this means * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to * // date; if you have any other folders that you want to ignore for performance reasons (gradle * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ * // for example, you might want to remove it from here. * inputExcludes: ["android/**", "ios/**"], * * // override which node gets called and with what additional arguments * nodeExecutableAndArgs: ["node"], * * // supply additional arguments to the packager * extraPackagerArgs: [] * ] */ project.ext.react = [ enableHermes: true, // clean and rebuild if changing cliPath: ["node", "-e", "console.log(require('react-native/cli').bin);"].execute([], projectDir).text.trim(), ] apply from: new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), "../react.gradle") /** * Set this to true to create two separate APKs instead of one: * - An APK that only works on ARM devices * - An APK that only works on x86 devices * The advantage is the size of the APK is reduced by about 4MB. * Upload all the APKs to the Play Store and people will download * the correct one based on the CPU architecture of their device. */ def enableSeparateBuildPerCPUArchitecture = false /** * Run Proguard to shrink the Java bytecode in release builds. */ def enableProguardInReleaseBuilds = false /** * The preferred build flavor of JavaScriptCore. * * For example, to use the international variant, you can use: * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` * * The international variant includes ICU i18n library and necessary data * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that * give correct results when using with locales other than en-US. Note that * this variant is about 6MiB larger per architecture than default. */ def jscFlavor = 'org.webkit:android-jsc:+' /** * Whether to enable the Hermes VM. * * This should be set on project.ext.react and that value will be read here. If it is not set * on project.ext.react, JavaScript will not be compiled to Hermes Bytecode * and the benefits of using Hermes will therefore be sharply reduced. */ def enableHermes = project.ext.react.get("enableHermes", false) /** * Architectures to build native code for. */ def reactNativeArchitectures() { def value = project.getProperties().get("reactNativeArchitectures") return value ? value.split(",") : ["armeabi-v7a", "x86_64", "arm64-v8a"] } def customDownloadsDir = System.getenv("REACT_NATIVE_DOWNLOADS_DIR") def dependenciesPath = System.getenv("REACT_NATIVE_DEPENDENCIES") def downloadsDir = customDownloadsDir ? new File(customDownloadsDir) : new File("$buildDir/downloads") def thirdPartyNdkDir = new File("$buildDir/third-party-ndk") task createNativeDepsDirectories { downloadsDir.mkdirs() thirdPartyNdkDir.mkdirs() } def REACT_NATIVE_DIR = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).parent def reactNativeThirdParty = new File("$REACT_NATIVE_DIR/ReactAndroid/src/main/jni/third-party") def reactProperties = new Properties() file("$REACT_NATIVE_DIR/ReactAndroid/gradle.properties").withInputStream { reactProperties.load(it) } def FOLLY_VERSION = reactProperties.getProperty("FOLLY_VERSION") def BOOST_VERSION = reactProperties.getProperty("BOOST_VERSION") def DOUBLE_CONVERSION_VERSION = reactProperties.getProperty("DOUBLE_CONVERSION_VERSION") // FOLLY task downloadFolly(dependsOn: createNativeDepsDirectories, type: Download) { src("https://github.com/facebook/folly/archive/v${FOLLY_VERSION}.tar.gz") onlyIfNewer(true) overwrite(false) dest(new File(downloadsDir, "folly-${FOLLY_VERSION}.tar.gz")) } task prepareFolly(dependsOn: [downloadFolly], type: Copy) { from(tarTree(downloadFolly.dest)) from("$reactNativeThirdParty/folly/Android.mk") include("folly-${FOLLY_VERSION}/folly/**/*", "Android.mk") eachFile { fname -> fname.path = (fname.path - "folly-${FOLLY_VERSION}/") } includeEmptyDirs = false into("$thirdPartyNdkDir/folly") } // GLOG task downloadGlog(dependsOn: createNativeDepsDirectories, type: Download) { src("https://github.com/google/glog/archive/v${GLOG_VERSION}.tar.gz") onlyIfNewer(true) overwrite(false) dest(new File(downloadsDir, "glog-${GLOG_VERSION}.tar.gz")) } task prepareGlog(dependsOn: dependenciesPath ? [] : [downloadGlog], type: Copy) { from(dependenciesPath ?: tarTree(downloadGlog.dest)) include("glog-${GLOG_VERSION}/**/*") includeEmptyDirs = false into("$thirdPartyNdkDir/glog") } // BOOST // The Boost library is a very large download (>100MB). // If Boost is already present on your system, define the REACT_NATIVE_BOOST_PATH env variable // and the build will use that. def boostPath = dependenciesPath ?: System.getenv("REACT_NATIVE_BOOST_PATH") task downloadBoost(dependsOn: createNativeDepsDirectories, type: Download) { src("https://archives.boost.io/release/${BOOST_VERSION.replace("_", ".")}/source/boost_${BOOST_VERSION}.tar.gz") onlyIfNewer(true) overwrite(false) dest(new File(downloadsDir, "boost_${BOOST_VERSION}.tar.gz")) } task prepareBoost(dependsOn: [downloadBoost], type: Copy) { from(tarTree(resources.gzip(downloadBoost.dest))) from("$reactNativeThirdParty/boost/Android.mk") include("Android.mk", "boost_${BOOST_VERSION}/boost/**/*.hpp", "boost/boost/**/*.hpp") includeEmptyDirs = false into("$thirdPartyNdkDir/boost") doLast { file("$thirdPartyNdkDir/boost/boost").renameTo("$thirdPartyNdkDir/boost/boost_${BOOST_VERSION}") } } // DOUBLE-CONVERSION task downloadDoubleConversion(dependsOn: createNativeDepsDirectories, type: Download) { src("https://github.com/google/double-conversion/archive/v${DOUBLE_CONVERSION_VERSION}.tar.gz") onlyIfNewer(true) overwrite(false) dest(new File(downloadsDir, "double-conversion-${DOUBLE_CONVERSION_VERSION}.tar.gz")) } task prepareDoubleConversion(dependsOn: [downloadDoubleConversion], type: Copy) { from(tarTree(downloadDoubleConversion.dest)) from("$reactNativeThirdParty/double-conversion/Android.mk") include("double-conversion-${DOUBLE_CONVERSION_VERSION}/src/**/*", "Android.mk") filesMatching("*/src/**/*", { fname -> fname.path = "double-conversion/${fname.name}" }) includeEmptyDirs = false into("$thirdPartyNdkDir/double-conversion") } // OPENSSL def hostSystem = System.getProperty('os.name').toLowerCase(Locale.ROOT) def hostTag = hostSystem.contains('mac') ? 'darwin-x86_64' : 'linux-x86_64' task downloadOpenSSL(dependsOn: createNativeDepsDirectories, type: Download) { src("https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz") onlyIfNewer(true) overwrite(false) dest(new File(downloadsDir, "openssl-${OPENSSL_VERSION}.tar.gz")) } task prepareOpenSSL( dependsOn: dependenciesPath ? [] : [downloadOpenSSL], type: Copy ) { from(dependenciesPath ?: tarTree(downloadOpenSSL.dest)) include("openssl-${OPENSSL_VERSION}/**/*") includeEmptyDirs = false into("${thirdPartyNdkDir}/openssl") } task prebuildOpenSSL(dependsOn: dependenciesPath ? [] : [prepareOpenSSL]) { inputs.properties([ 'openssl.version': OPENSSL_VERSION, 'openssl.abis': getBuildTypeABIs(reactNativeArchitectures()) ]) outputs.dir("${thirdPartyNdkDir}/openssl/openssl-${OPENSSL_VERSION}/build/") .withPropertyName('openssl.output') doFirst { getBuildTypeABIs(reactNativeArchitectures()).each { buildABI -> logger.info("Building OpenSSL library for the ${buildABI}") exec { commandLine './bash/build_openssl.sh', // OPENSSL_SUBMODULE_PATH "${thirdPartyNdkDir}/openssl/openssl-${OPENSSL_VERSION}/", // HOST_TAG hostTag, // ANDROID_ARCH_ABI "${buildABI}", // MIN_SDK_VERSION rootProject.ext.minSdkVersion, // ANDROID_NDK_HOME android.ndkDirectory.absolutePath, // PARALLEL_THREADS Runtime.getRuntime().availableProcessors() } } } } // JNI def REACT_NATIVE_AAR = "${buildDir}/react-native.aar" def extractReactNativeAAR = { buildType -> def suffix = buildType == 'Debug' ? '-debug' : '-release' def rnAARs = fileTree(REACT_NATIVE_DIR).matching { include "**/react-native/**/*${suffix}.aar" } if (rnAARs.isEmpty()) { rnAARs = fileTree(REACT_NATIVE_DIR).matching { include "**/react-native/**/*.aar" } } if (rnAARs.any() && rnAARs.size() > 1) { logger.error("More than one React Native AAR file has been found:") rnAARs.each { println(it) } throw new GradleException( "Multiple React Native AARs found:\n${rnAARs.join("\n")}" + "\nRemove the old ones and try again" ) } def rnAAR = rnAARs.singleFile def file = rnAAR.absoluteFile def packageName = file.name.tokenize('-')[0] copy { from zipTree(file) into REACT_NATIVE_AAR include "jni/**/*" } } task extractReactNativeAARRelease { doLast { extractReactNativeAAR('Release') } } task extractReactNativeAARDebug { doLast { extractReactNativeAAR('Debug') } } task extractAARHeaders { doLast { configurations.extractHeaders.files.each { def file = it.absoluteFile copy { from zipTree(file) into "$buildDir/$file.name" include "**/*.h" } } } } task extractJNIFiles { doLast { configurations.extractJNI.files.each { def file = it.absoluteFile copy { from zipTree(file) into "$buildDir/$file.name" include "jni/**/*" } } } } tasks.whenTaskAdded { task -> if ( !task.name.contains("Clean") && (task.name.contains('externalNativeBuild') || task.name.startsWith('configureCMake') || task.name.startsWith('buildCMake')) ) { def buildType = task.name.endsWith('Debug') ? 'Debug' : 'Release' task.dependsOn(extractAARHeaders) task.dependsOn(extractJNIFiles) task.dependsOn("extractReactNativeAAR${buildType}") } } // EXTERNAL LIBS task prepareExternalLibs { dependsOn prepareFolly dependsOn prepareGlog dependsOn prepareBoost dependsOn prepareDoubleConversion dependsOn prebuildOpenSSL } // Removes the '.cxx' directory to prevent running // ninja clean when the 'clean' command is executed task removeCXX(type: Exec) { commandLine 'rm', '-rf', '.cxx' } // Add cross-compilation targets to Rust toolchain task updateRustToolchain(type: Exec) { commandLine "rustup", "target", "add", "aarch64-linux-android", "armv7-linux-androideabi", "x86_64-linux-android" } def nativeRustLibraryDir = "../../native_rust_library" def nativeRustLibraryManifestPath = "${nativeRustLibraryDir}/Cargo.toml" def cxxBridgeBindingDir = "${nativeRustLibraryDir}/target/cxxbridge/native_rust_library/src" def cxxBridgeCommonDir = "${nativeRustLibraryDir}/target/cxxbridge/rust" task buildNativeRustLibrary(type: Exec) { commandLine "cargo", "build", "--manifest-path", nativeRustLibraryManifestPath } task copyNativeRustLibraryFiles(dependsOn: buildNativeRustLibrary, type: Copy) { from(cxxBridgeBindingDir) { include 'lib.rs.h' include 'lib.rs.cc' } from(cxxBridgeCommonDir) { include 'cxx.h' } into nativeRustLibraryDir } // Bind preBuild dependencies only if not 'clean' running if (!isCleanRunning()) { afterEvaluate { preBuild.dependsOn(prepareExternalLibs, updateRustToolchain, copyNativeRustLibraryFiles) } } // Run removing CXX task before the clean execution beforeEvaluate { clean.dependsOn(removeCXX) } // Detects are we running the 'clean' commands def isCleanRunning() { gradle.startParameter.taskRequests.any { !it.args.isEmpty() && it.args.first().startsWith('clean') } } // Release keystore via macOS Keychain Access def getPassword(String keyLabel) { if (System.getenv('ANDROID_SIGNING_PASSWORD')) { return System.getenv('ANDROID_SIGNING_PASSWORD') } def stdout = new ByteArrayOutputStream() exec { commandLine 'security', 'find-generic-password', '-wl', keyLabel, '-a', System.properties['user.name'] standardOutput = stdout ignoreExitValue true } return stdout.toString().strip() } // Returns all ABIs architectures for the 'bundleRelease' // or only from 'adb devices' if running debug release. def getBuildTypeABIs(nativeArchitectures) { if (System.getenv("BUILDKITE") == "true") { return ["arm64-v8a"] } def isBundleRelease = gradle.startParameter.taskRequests.any { !it.args.isEmpty() && it.args.first().contains("bundleRelease") } if (isBundleRelease) { // All of the supported ABIs // https://developer.android.com/ndk/guides/abis.html#sa final allAbis = ["armeabi-v7a", "arm64-v8a", "x86_64"] logger.info("Using all architectures to build: ${allAbis}") return allAbis } if (nativeArchitectures) { return nativeArchitectures } // Get current 'adb devices' architectures def commandOutput = new ByteArrayOutputStream() exec { commandLine "./bash/detect_abis.sh" standardOutput = commandOutput } final detectedAbis = commandOutput.toString("UTF-8").trim().tokenize() logger.info("Detected architectures to build: ${detectedAbis}") return detectedAbis } def REACT_NATIVE_SO_DIR = "${REACT_NATIVE_AAR}/jni" android { buildFeatures { prefab true } configurations { all*.exclude module: 'fbjni-java-only' extractHeaders extractJNI } dependencies { implementation 'com.facebook.fbjni:fbjni:0.2.2' compileOnly 'com.facebook.fbjni:fbjni:0.2.2' extractHeaders 'com.facebook.fbjni:fbjni:0.2.2:headers' extractJNI 'com.facebook.fbjni:fbjni:0.2.2' } ndkVersion rootProject.ext.ndkVersion compileSdkVersion rootProject.ext.compileSdkVersion defaultConfig { applicationId 'app.comm.android' minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 328 versionName '1.0.328' buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() if (isNewArchitectureEnabled()) { // We configure the CMake build only if you decide to opt-in for the New Architecture. externalNativeBuild { cmake { arguments "-DPROJECT_BUILD_DIR=$buildDir", "-DREACT_ANDROID_DIR=$rootDir/../node_modules/react-native/ReactAndroid", "-DREACT_ANDROID_BUILD_DIR=$rootDir/../node_modules/react-native/ReactAndroid/build", "-DNODE_MODULES_DIR=$rootDir/../node_modules", "-DANDROID_STL=c++_shared" } } if (!enableSeparateBuildPerCPUArchitecture) { ndk { abiFilters (*reactNativeArchitectures()) } } } missingDimensionStrategy 'react-native-camera', 'general' multiDexEnabled true } if (isNewArchitectureEnabled()) { // We configure the NDK build only if you decide to opt-in for the New Architecture. externalNativeBuild { cmake { path "$projectDir/src/main/jni/CMakeLists.txt" } } def reactAndroidProjectDir = project(':ReactAndroid').projectDir def packageReactNdkDebugLibs = tasks.register("packageReactNdkDebugLibs", Copy) { dependsOn(":ReactAndroid:packageReactNdkDebugLibsForBuck") from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib") into("$buildDir/react-ndk/exported") } def packageReactNdkReleaseLibs = tasks.register("packageReactNdkReleaseLibs", Copy) { dependsOn(":ReactAndroid:packageReactNdkReleaseLibsForBuck") from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib") into("$buildDir/react-ndk/exported") } afterEvaluate { // If you wish to add a custom TurboModule or component locally, // you should uncomment this line. // preBuild.dependsOn("generateCodegenArtifactsFromSchema") preDebugBuild.dependsOn(packageReactNdkDebugLibs) preReleaseBuild.dependsOn(packageReactNdkReleaseLibs) // Due to a bug inside AGP, we have to explicitly set a dependency // between configureCMakeDebug* tasks and the preBuild tasks. // This can be removed once this is solved: https://issuetracker.google.com/issues/207403732 configureCMakeRelWithDebInfo.dependsOn(preReleaseBuild) configureCMakeDebug.dependsOn(preDebugBuild) reactNativeArchitectures().each { architecture -> tasks.findByName("configureCMakeDebug[${architecture}]")?.configure { dependsOn("preDebugBuild") } tasks.findByName("configureCMakeRelWithDebInfo[${architecture}]")?.configure { dependsOn("preReleaseBuild") } } } } splits { abi { reset() enable enableSeparateBuildPerCPUArchitecture universalApk false // If true, also generate a universal APK include (*reactNativeArchitectures()) } } signingConfigs { debug { storeFile file('debug.keystore') storePassword 'android' keyAlias 'androiddebugkey' keyPassword 'android' } release { if (project.hasProperty('COMM_UPLOAD_STORE_FILE')) { def password = getPassword('CommAndroidKeyPassword') storeFile file(COMM_UPLOAD_STORE_FILE) storePassword password keyAlias COMM_UPLOAD_KEY_ALIAS keyPassword password } } } buildTypes { final buildABIs = getBuildTypeABIs(reactNativeArchitectures()) release { if (project.hasProperty('COMM_UPLOAD_STORE_FILE')) { signingConfig signingConfigs.release } else { signingConfig signingConfigs.debug } minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" ndk { abiFilters = [] abiFilters.addAll(buildABIs) } } debug { signingConfig signingConfigs.debug ndk { abiFilters = [] abiFilters.addAll(buildABIs) } } } packagingOptions { pickFirst "**/libc++_shared.so" pickFirst "**/libfbjni.so" } defaultConfig { externalNativeBuild { cmake { arguments "-DANDROID_STL=c++_shared", "-DGLOG_VERSION=" + GLOG_VERSION, "-DOPENSSL_VERSION=" + OPENSSL_VERSION, "-DNDK_VERSION=" + rootProject.ext.ndkVersion, "-DREACT_NATIVE_SO_DIR=${REACT_NATIVE_SO_DIR}", "-DBOOST_VERSION=${BOOST_VERSION}", "-DCI=${System.getenv('CI')}" targets "comm_jni_module", "turbomodulejsijni" } } } externalNativeBuild { cmake { path "CMakeLists.txt" version "3.18.1" } } // applicationVariants are e.g. debug, release applicationVariants.all { variant -> variant.outputs.each { output -> // For each separate APK per architecture, set a unique version code as described here: // https://developer.android.com/studio/build/configure-apk-splits.html // Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc. def versionCodes = ["armeabi-v7a": 1, "arm64-v8a": 3, "x86_64": 4] def abi = output.getFilter(OutputFile.ABI) if (abi != null) { // null for the universal-debug, universal-release variants output.versionCodeOverride = defaultConfig.versionCode * 1000 + versionCodes.get(abi) } } } afterEvaluate { extractAARHeaders.dependsOn(prepareExternalLibs) extractJNIFiles.dependsOn(prepareExternalLibs) } } dependencies { + implementation 'com.tencent:mmkv:1.3.3' implementation fileTree(dir: "libs", include: ["*.jar"]) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10" //noinspection GradleDynamicVersion implementation("com.facebook.react:react-native:+") def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true"; def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true"; def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true"; // If your app supports Android versions before Ice Cream Sandwich (API level 14) // All fresco packages should use the same version if (isGifEnabled || isWebpEnabled) { implementation 'com.facebook.fresco:fresco:2.5.0' implementation 'com.facebook.fresco:imagepipeline-okhttp3:2.5.0' } if (isGifEnabled) { // For animated gif support implementation 'com.facebook.fresco:animated-gif:2.5.0' } if (isWebpEnabled) { // For webp support implementation 'com.facebook.fresco:webpsupport:2.5.0' if (isWebpAnimatedEnabled) { // Animated webp support implementation 'com.facebook.fresco:animated-webp:2.5.0' } } implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" implementation "com.google.android.gms:play-services-base:16.1.0" implementation "com.google.firebase:firebase-core:21.1.0" implementation "com.google.firebase:firebase-messaging:21.1.0" implementation "me.leolin:ShortcutBadger:1.1.21@aar" implementation project(':reactnativekeyboardinput') implementation "androidx.multidex:multidex:2.0.1" implementation "androidx.lifecycle:lifecycle-process:2.5.1" implementation 'com.facebook.fresco:fresco:2.5.0' implementation 'com.facebook.fresco:animated-gif:2.5.0' implementation 'com.facebook.fresco:animated-webp:2.5.0' implementation 'com.facebook.fresco:webpsupport:2.5.0' implementation 'org.conscrypt:conscrypt-android:2.0.0' if (enableHermes) { //noinspection GradleDynamicVersion implementation("com.facebook.react:hermes-engine:+") { // From node_modules exclude group:'com.facebook.fbjni' } } else { implementation jscFlavor } } if (isNewArchitectureEnabled()) { // If new architecture is enabled, we let you build RN from source // Otherwise we fallback to a prebuilt .aar bundled in the NPM package. // This will be applied to all the imported transtitive dependency. configurations.all { resolutionStrategy.dependencySubstitution { substitute(module("com.facebook.react:react-native")) .using(project(":ReactAndroid")) .because("On New Architecture we're building React Native from source") substitute(module("com.facebook.react:hermes-engine")) .using(project(":ReactAndroid:hermes-engine")) .because("On New Architecture we're building Hermes from source") } } } // Run this once to be able to run the application with BUCK // puts all compile dependencies into folder libs for BUCK to use task copyDownloadableDepsToLibs(type: Copy) { from configurations.implementation into 'libs' } apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json')"].execute(null, rootDir).text.trim(), "../native_modules.gradle"); applyNativeModulesAppBuildGradle(project) def isNewArchitectureEnabled() { // To opt-in for the New Architecture, you can either: // - Set `newArchEnabled` to true inside the `gradle.properties` file // - Invoke gradle with `-newArchEnabled=true` // - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true` return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" } apply plugin: 'com.google.gms.google-services' diff --git a/native/android/app/src/cpp/CommMMKV.cpp b/native/android/app/src/cpp/CommMMKV.cpp new file mode 100644 index 000000000..9d9c882b6 --- /dev/null +++ b/native/android/app/src/cpp/CommMMKV.cpp @@ -0,0 +1,68 @@ +#include "jniHelpers.h" +#include +#include + +using namespace facebook::jni; + +class CommMMKVJavaClass : public JavaClass { +public: + static auto constexpr kJavaDescriptor = "Lapp/comm/android/fbjni/CommMMKV;"; + + static void initialize() { + static const auto cls = javaClassStatic(); + static auto method = cls->getStaticMethod("initialize"); + method(cls); + } + + static void clearSensitiveData() { + static const auto cls = javaClassStatic(); + static auto method = cls->getStaticMethod("clearSensitiveData"); + method(cls); + } + + static bool setString(std::string key, std::string value) { + static const auto cls = javaClassStatic(); + static auto method = + cls->getStaticMethod("setString"); + return method(cls, key, value); + } + + static std::optional getString(std::string key) { + static const auto cls = javaClassStatic(); + static auto method = + cls->getStaticMethod("getString"); + const auto result = method(cls, key); + if (result) { + return result->toStdString(); + } + return std::nullopt; + } +}; + +namespace comm { + +void CommMMKV::initialize() { + NativeAndroidAccessProvider::runTask( + []() { CommMMKVJavaClass::initialize(); }); +} + +void CommMMKV::clearSensitiveData() { + NativeAndroidAccessProvider::runTask( + []() { CommMMKVJavaClass::clearSensitiveData(); }); +} + +bool CommMMKV::setString(std::string key, std::string value) { + bool result; + NativeAndroidAccessProvider::runTask( + [&]() { result = CommMMKVJavaClass::setString(key, value); }); + return result; +} + +std::optional CommMMKV::getString(std::string key) { + std::optional result; + NativeAndroidAccessProvider::runTask( + [&]() { result = CommMMKVJavaClass::getString(key); }); + return result; +} + +} // namespace comm diff --git a/native/android/app/src/main/java/app/comm/android/MainApplication.java b/native/android/app/src/main/java/app/comm/android/MainApplication.java index 428d42b56..3c52232d0 100644 --- a/native/android/app/src/main/java/app/comm/android/MainApplication.java +++ b/native/android/app/src/main/java/app/comm/android/MainApplication.java @@ -1,126 +1,128 @@ package app.comm.android; import android.content.Context; import android.content.res.Configuration; import android.database.CursorWindow; import androidx.annotation.NonNull; import androidx.multidex.MultiDexApplication; import app.comm.android.commservices.CommServicesPackage; +import app.comm.android.fbjni.CommMMKV; import app.comm.android.fbjni.CommSecureStore; import app.comm.android.fbjni.DatabaseInitializer; import app.comm.android.fbjni.GlobalDBSingleton; import app.comm.android.newarchitecture.MainApplicationReactNativeHost; import app.comm.android.notifications.CommAndroidNotificationsPackage; import com.facebook.react.PackageList; import com.facebook.react.ReactApplication; import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactPackage; import com.facebook.react.bridge.JSIModulePackage; import com.facebook.react.config.ReactFeatureFlags; import com.facebook.soloader.SoLoader; import com.wix.reactnativekeyboardinput.KeyboardInputPackage; import expo.modules.ApplicationLifecycleDispatcher; import expo.modules.ReactNativeHostWrapper; import java.io.File; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.security.Security; import java.util.List; public class MainApplication extends MultiDexApplication implements ReactApplication { static { System.loadLibrary("fbjni"); System.loadLibrary("comm_jni_module"); } private static Context context; private final ReactNativeHost mReactNativeHost = new ReactNativeHostWrapper(this, new ReactNativeHost(this) { @Override public boolean getUseDeveloperSupport() { return BuildConfig.DEBUG; } @Override protected List getPackages() { @SuppressWarnings("UnnecessaryLocalVariable") List packages = new PackageList(this).getPackages(); packages.add(new KeyboardInputPackage(this.getApplication())); packages.add(new CommAndroidNotificationsPackage()); packages.add(new CommServicesPackage()); return packages; } @Override protected String getJSMainModuleName() { return "index"; } @Override protected JSIModulePackage getJSIModulePackage() { return new CommCoreJSIModulePackage(); } }); private final ReactNativeHost mNewArchitectureNativeHost = new ReactNativeHostWrapper( this, new MainApplicationReactNativeHost(this)); @Override public ReactNativeHost getReactNativeHost() { if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { return mNewArchitectureNativeHost; } else { return mReactNativeHost; } } @Override public void onCreate() { super.onCreate(); // If you opted-in for the New Architecture, we enable the TurboModule // system MainApplication.context = this.getApplicationContext(); ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; Security.insertProviderAt(new org.conscrypt.OpenSSLProvider(), 1); SoLoader.init(this, /* native exopackage */ false); this.initializeDatabase(); + CommMMKV.initialize(); ApplicationLifecycleDispatcher.onApplicationCreate(this); try { Field field = CursorWindow.class.getDeclaredField("sCursorWindowSize"); field.setAccessible(true); field.set(null, 100 * 1024 * 1024); // 100 MiB } catch (Exception e) { if (BuildConfig.DEBUG) { e.printStackTrace(); } } } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig); } public static Context getMainApplicationContext() { return MainApplication.context; } private void initializeDatabase() { File sqliteFile = this.getApplicationContext().getDatabasePath("comm.sqlite"); CommSecureStore.getInstance().initialize( ExpoUtils.createExpoSecureStoreSupplier(this.getApplicationContext())); GlobalDBSingleton.scheduleOrRun(() -> { DatabaseInitializer.initializeDatabaseManager(sqliteFile.getPath()); }); } } diff --git a/native/android/app/src/main/java/app/comm/android/fbjni/CommMMKV.java b/native/android/app/src/main/java/app/comm/android/fbjni/CommMMKV.java new file mode 100644 index 000000000..92225e989 --- /dev/null +++ b/native/android/app/src/main/java/app/comm/android/fbjni/CommMMKV.java @@ -0,0 +1,98 @@ +package app.comm.android.fbjni; + +import app.comm.android.MainApplication; +import app.comm.android.fbjni.CommSecureStore; +import app.comm.android.fbjni.PlatformSpecificTools; +import com.tencent.mmkv.MMKV; +import java.util.Base64; + +public class CommMMKV { + private static final int MMKV_ENCRYPTION_KEY_SIZE = 16; + private static final int MMKV_ID_SIZE = 8; + + private static final String SECURE_STORE_MMKV_ENCRYPTION_KEY_ID = + "comm.mmkvEncryptionKey"; + private static final String SECURE_STORE_MMKV_IDENTIFIER_KEY_ID = + "comm.mmkvID"; + + private static String mmkvEncryptionKey; + private static String mmkvIdentifier; + + private static MMKV getMMKVInstance(String mmkvID, String encryptionKey) { + MMKV mmkv = + MMKV.mmkvWithID(mmkvID, MMKV.SINGLE_PROCESS_MODE, encryptionKey); + if (mmkv == null) { + throw new RuntimeException("Failed to instantiate MMKV object."); + } + return mmkv; + } + + private static void assignInitializationData() { + byte[] encryptionKeyBytes = PlatformSpecificTools.generateSecureRandomBytes( + MMKV_ENCRYPTION_KEY_SIZE); + byte[] identifierBytes = + PlatformSpecificTools.generateSecureRandomBytes(MMKV_ID_SIZE); + String encryptionKey = Base64.getEncoder() + .encodeToString(encryptionKeyBytes) + .substring(0, MMKV_ENCRYPTION_KEY_SIZE); + String identifier = Base64.getEncoder() + .encodeToString(identifierBytes) + .substring(0, MMKV_ID_SIZE); + CommSecureStore.set(SECURE_STORE_MMKV_ENCRYPTION_KEY_ID, encryptionKey); + CommSecureStore.set(SECURE_STORE_MMKV_IDENTIFIER_KEY_ID, identifier); + mmkvEncryptionKey = encryptionKey; + mmkvIdentifier = identifier; + } + + public static void initialize() { + if (mmkvEncryptionKey != null && mmkvIdentifier != null) { + return; + } + + synchronized (CommMMKV.class) { + if (mmkvEncryptionKey != null && mmkvIdentifier != null) { + return; + } + + String encryptionKey = + CommSecureStore.get(SECURE_STORE_MMKV_ENCRYPTION_KEY_ID); + String identifier = + CommSecureStore.get(SECURE_STORE_MMKV_IDENTIFIER_KEY_ID); + + if (encryptionKey == null || identifier == null) { + assignInitializationData(); + } else { + mmkvEncryptionKey = encryptionKey; + mmkvIdentifier = identifier; + } + + MMKV.initialize(MainApplication.getMainApplicationContext()); + getMMKVInstance(mmkvIdentifier, mmkvEncryptionKey); + } + } + + public static void clearSensitiveData() { + initialize(); + synchronized (mmkvEncryptionKey) { + getMMKVInstance(mmkvIdentifier, mmkvEncryptionKey).clearAll(); + boolean storageRemoved = MMKV.removeStorage(mmkvIdentifier); + if (!storageRemoved) { + throw new RuntimeException("Failed to remove MMKV storage."); + } + assignInitializationData(); + MMKV.initialize(MainApplication.getMainApplicationContext()); + getMMKVInstance(mmkvIdentifier, mmkvEncryptionKey); + } + } + + public static boolean setString(String key, String value) { + initialize(); + return getMMKVInstance(mmkvIdentifier, mmkvEncryptionKey) + .encode(key, value); + } + + public static String getString(String key) { + initialize(); + return getMMKVInstance(mmkvIdentifier, mmkvEncryptionKey).decodeString(key); + } +} diff --git a/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp b/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp index 9e6244947..8b10b090c 100644 --- a/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp +++ b/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp @@ -1,104 +1,100 @@ #include "DatabaseManager.h" #include "../Notifications/BackgroundDataStorage/NotificationsCryptoModule.h" -#ifdef __APPLE__ #include "../Tools/CommMMKV.h" -#endif #include "../Tools/CommSecureStore.h" #include "Logger.h" #include "PlatformSpecificTools.h" #include "SQLiteQueryExecutor.h" namespace comm { std::once_flag DatabaseManager::initialized; typedef const std::string DatabaseManagerStatus; DatabaseManagerStatus DB_MANAGER_WORKABLE = "WORKABLE"; DatabaseManagerStatus DB_MANAGER_FIRST_FAILURE = "FIRST_FAILURE"; DatabaseManagerStatus DB_MANAGER_SECOND_FAILURE = "SECOND_FAILURE"; DatabaseManagerStatus DB_OPERATIONS_FAILURE = "DB_OPERATIONS_FAILURE"; const std::string DATABASE_MANAGER_STATUS_KEY = "DATABASE_MANAGER_STATUS"; const DatabaseQueryExecutor &DatabaseManager::getQueryExecutor() { // TODO: conditionally create desired type of db manager // maybe basing on some preprocessor flag thread_local SQLiteQueryExecutor instance; // creating an instance means that migration code was executed // and finished without error and database is workable std::call_once(DatabaseManager::initialized, []() { DatabaseManager::indicateQueryExecutorCreation(); }); return instance; } void DatabaseManager::clearSensitiveData() { CommSecureStore::set(CommSecureStore::userID, ""); CommSecureStore::set(CommSecureStore::deviceID, ""); CommSecureStore::set(CommSecureStore::commServicesAccessToken, ""); SQLiteQueryExecutor::clearSensitiveData(); PlatformSpecificTools::removeBackupDirectory(); -#ifdef __APPLE__ CommMMKV::clearSensitiveData(); -#endif NotificationsCryptoModule::clearSensitiveData(); DatabaseManager::setDatabaseStatusAsWorkable(); } void DatabaseManager::initializeQueryExecutor(std::string &databasePath) { try { SQLiteQueryExecutor::initialize(databasePath); DatabaseManager::getQueryExecutor(); DatabaseManager::indicateQueryExecutorCreation(); Logger::log("Database manager initialized"); } catch (...) { folly::Optional databaseManagerStatus = CommSecureStore::get(DATABASE_MANAGER_STATUS_KEY); if (!databaseManagerStatus.hasValue() || databaseManagerStatus.value() == DB_MANAGER_WORKABLE) { CommSecureStore::set( DATABASE_MANAGER_STATUS_KEY, DB_MANAGER_FIRST_FAILURE); Logger::log("Database manager initialization issue, terminating app"); throw; } if (databaseManagerStatus.value() == DB_MANAGER_FIRST_FAILURE) { CommSecureStore::set( DATABASE_MANAGER_STATUS_KEY, DB_MANAGER_SECOND_FAILURE); Logger::log( "Database manager initialization issue, app proceeding, but " "database needs to be deleted"); return; } } } void DatabaseManager::setDatabaseStatusAsWorkable() { CommSecureStore::set(DATABASE_MANAGER_STATUS_KEY, DB_MANAGER_WORKABLE); } void DatabaseManager::indicateQueryExecutorCreation() { folly::Optional databaseManagerStatus = CommSecureStore::get(DATABASE_MANAGER_STATUS_KEY); if (!databaseManagerStatus.hasValue() || databaseManagerStatus.value() != DB_OPERATIONS_FAILURE) { // creating query executor means that schema was created without error, // but this doesn't imply that schema has a proper structure, // and operation will not crash, this case should not be overridden CommSecureStore::set(DATABASE_MANAGER_STATUS_KEY, DB_MANAGER_WORKABLE); } } bool DatabaseManager::checkIfDatabaseNeedsDeletion() { folly::Optional databaseManagerStatus = CommSecureStore::get(DATABASE_MANAGER_STATUS_KEY); return databaseManagerStatus.hasValue() && (databaseManagerStatus.value() == DB_MANAGER_SECOND_FAILURE || databaseManagerStatus.value() == DB_OPERATIONS_FAILURE); } void DatabaseManager::reportDBOperationsFailure() { CommSecureStore::set(DATABASE_MANAGER_STATUS_KEY, DB_OPERATIONS_FAILURE); } } // namespace comm