package expo.modules.updates import android.content.Context import com.facebook.react.ReactApplication import expo.modules.updates.events.IUpdatesEventManagerObserver import expo.modules.updates.loader.LoaderTask import expo.modules.updates.logging.UpdatesErrorCode import expo.modules.updates.logging.UpdatesLogger import expo.modules.updatesinterface.UpdatesControllerRegistry import java.lang.ref.WeakReference /** * Main entry point to expo-updates. Singleton that keeps track of updates state, holds references * to instances of other updates classes, and is the central hub for all updates-related tasks. * * The `start` method in the singleton instance of [IUpdatesController] should be invoked early in * the application lifecycle, via [UpdatesPackage]. It delegates to an instance of [LoaderTask] to * start the process of loading and launching an update, then responds appropriately depending on * the callbacks that are invoked. */ object UpdatesController { @Volatile private var singletonInstance: IUpdatesController? = null @Volatile private var overrideConfiguration: UpdatesConfiguration? = null @JvmStatic val instance: IUpdatesController get() { return checkNotNull(singletonInstance) { "UpdatesController.instance was called before the module was initialized" } } @JvmStatic fun initializeWithoutStarting(context: Context, useDeveloperSupport: Boolean) { if (singletonInstance != null) { return } if (useDeveloperSupport && !UpdatesPackage.isUsingNativeDebug) { if (BuildConfig.USE_DEV_CLIENT) { val devLauncherController = initializeAsDevLauncherWithoutStarting(context) singletonInstance = devLauncherController UpdatesControllerRegistry.controller = WeakReference(devLauncherController) } else { singletonInstance = DisabledUpdatesController(context, null) } return } val logger = UpdatesLogger(context.filesDir) val updatesDirectory = try { UpdatesUtils.getOrCreateUpdatesDirectory(context) } catch (e: Exception) { logger.error( "The expo-updates system is disabled due to a storage access error", e, UpdatesErrorCode.InitializationError ) singletonInstance = DisabledUpdatesController(context, e) return } val updatesConfiguration: UpdatesConfiguration? = overrideConfiguration ?: run { when (UpdatesConfiguration.getUpdatesConfigurationValidationResult(context, null)) { UpdatesConfigurationValidationResult.VALID -> { return@run UpdatesConfiguration(context, null) } UpdatesConfigurationValidationResult.INVALID_NOT_ENABLED -> logger.warn( "The expo-updates system is explicitly disabled. To enable it, set the enabled setting to true.", UpdatesErrorCode.InitializationError ) UpdatesConfigurationValidationResult.INVALID_MISSING_URL -> logger.warn( "The expo-updates system is disabled due to an invalid configuration. Ensure a valid URL is supplied.", UpdatesErrorCode.InitializationError ) UpdatesConfigurationValidationResult.INVALID_MISSING_RUNTIME_VERSION -> logger.warn( "The expo-updates system is disabled due to an invalid configuration. Ensure a runtime version is supplied.", UpdatesErrorCode.InitializationError ) } return@run null } singletonInstance = if (updatesConfiguration != null) { val enabledUpdatesController = EnabledUpdatesController(context, updatesConfiguration, updatesDirectory) UpdatesControllerRegistry.controller = WeakReference(enabledUpdatesController) enabledUpdatesController } else { val disabledUpdatesController = DisabledUpdatesController(context, null) UpdatesControllerRegistry.controller = WeakReference(disabledUpdatesController) disabledUpdatesController } } private fun initializeAsDevLauncherWithoutStarting(context: Context): UpdatesDevLauncherController { check(singletonInstance == null) { "UpdatesController must not be initialized prior to calling initializeAsDevLauncherWithoutStarting" } var updatesDirectoryException: Exception? = null val updatesDirectory = try { UpdatesUtils.getOrCreateUpdatesDirectory(context) } catch (e: Exception) { updatesDirectoryException = e null } val initialUpdatesConfiguration = overrideConfiguration ?: run { if (UpdatesConfiguration.getUpdatesConfigurationValidationResult( context, null ) == UpdatesConfigurationValidationResult.VALID ) { UpdatesConfiguration(context, null) } else { null } } val instance = UpdatesDevLauncherController( context, initialUpdatesConfiguration, updatesDirectory, updatesDirectoryException ) singletonInstance = instance return instance } /** * Initializes the UpdatesController singleton. This should be called as early as possible in the * application's lifecycle. Can pass additional configuration to this method to set or override * configuration values at runtime rather than just AndroidManifest.xml. * @param context the base context of the application, ideally a [ReactApplication] */ @JvmStatic @Synchronized fun initialize(context: Context, useDeveloperSupport: Boolean = false) { if (singletonInstance == null) { initializeWithoutStarting(context, useDeveloperSupport) singletonInstance?.start() } } /** * Overrides the [UpdatesConfiguration] that will be used inside [UpdatesController] * This should be called as early as possible in the application's lifecycle. * Can pass additional configuration to this method to set or override * configuration values at runtime rather than just AndroidManifest.xml. * * @param context the base context of the application, ideally a [ReactApplication] * @param configuration map of configuration pairs to override those from AndroidManifest.xml */ @JvmStatic fun overrideConfiguration(context: Context, configuration: Map) { if (singletonInstance != null) { throw AssertionError("The method should be called before UpdatesController.initialize()") } val updatesConfigurationValidationResult = UpdatesConfiguration.getUpdatesConfigurationValidationResult(context, configuration) if (updatesConfigurationValidationResult == UpdatesConfigurationValidationResult.VALID) { overrideConfiguration = UpdatesConfiguration(context, configuration) } else { val logger = UpdatesLogger(context.filesDir) logger.warn("Failed to overrideConfiguration: invalid configuration: ${updatesConfigurationValidationResult.name}") } } internal fun setUpdatesEventManagerObserver(observer: WeakReference) { singletonInstance?.eventManager?.observer = observer singletonInstance?.onEventListenerStartObserving() } internal fun removeUpdatesEventManagerObserver() { singletonInstance?.eventManager?.observer = null } }