<commit_before>/*
 * Copyright 2017 <NAME>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

package ee.ria.DigiDoc.android;

import android.arch.lifecycle.ViewModel;
import android.arch.lifecycle.ViewModelProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
import android.os.StrictMode;
import android.support.annotation.NonNull;

import com.google.common.collect.ImmutableList;
import com.jakewharton.threetenabp.AndroidThreeTen;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.security.Security;
import java.util.Date;
import java.util.Map;

import javax.inject.Provider;
import javax.inject.Singleton;

import dagger.Binds;
import dagger.BindsInstance;
import dagger.Component;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
import ee.ria.DigiDoc.BuildConfig;
import ee.ria.DigiDoc.R;
import ee.ria.DigiDoc.android.crypto.create.CryptoCreateViewModel;
import ee.ria.DigiDoc.android.eid.EIDHomeViewModel;
import ee.ria.DigiDoc.android.main.diagnostics.DiagnosticsView;
import ee.ria.DigiDoc.android.main.home.HomeViewModel;
import ee.ria.DigiDoc.android.signature.create.SignatureCreateViewModel;
import ee.ria.DigiDoc.android.signature.data.SignatureContainerDataSource;
import ee.ria.DigiDoc.android.signature.data.source.FileSystemSignatureContainerDataSource;
import ee.ria.DigiDoc.android.signature.list.SignatureListViewModel;
import ee.ria.DigiDoc.android.signature.update.SignatureUpdateViewModel;
import ee.ria.DigiDoc.android.utils.Formatter;
import ee.ria.DigiDoc.android.utils.LocaleService;
import ee.ria.DigiDoc.android.utils.navigator.Navigator;
import ee.ria.DigiDoc.android.utils.navigator.conductor.ConductorNavigator;
import ee.ria.DigiDoc.configuration.util.UserAgentUtil;
import ee.ria.DigiDoc.configuration.ConfigurationConstants;
import ee.ria.DigiDoc.configuration.ConfigurationManager;
import ee.ria.DigiDoc.configuration.ConfigurationManagerService;
import ee.ria.DigiDoc.configuration.ConfigurationProperties;
import ee.ria.DigiDoc.configuration.ConfigurationProvider;
import ee.ria.DigiDoc.configuration.loader.CachedConfigurationHandler;
import ee.ria.DigiDoc.configuration.util.FileUtils;
import ee.ria.DigiDoc.crypto.RecipientRepository;
import ee.ria.DigiDoc.sign.SignLib;
import ee.ria.DigiDoc.smartcardreader.SmartCardReaderManager;
import ee.ria.DigiDoc.smartcardreader.acs.AcsSmartCardReader;
import ee.ria.DigiDoc.smartcardreader.identiv.IdentivSmartCardReader;
import io.reactivex.plugins.RxJavaPlugins;
import timber.log.Timber;

public class Application extends android.app.Application {

    private static ConfigurationProvider configurationProvider;

    @Override
    public void onCreate() {
        setupTSLFiles();
        setupStrictMode();
        super.onCreate();
        setupBouncyCastle();
        setupTimber();
        setupThreeTenAbp();
        setupConfiguration();
        setupRxJava();
        setupDagger();
    }

    // TSL files
    private void setupTSLFiles() {
        String destination = getCacheDir().toString() + "/schema";
        String assetsPath = "tslFiles";
        String[] tslFiles = null;
        try {
            tslFiles = getAssets().list(assetsPath);
        } catch (IOException e) {
            Timber.e(e, "Failed to get folder list: %s", assetsPath);
        }

        if (tslFiles != null && tslFiles.length > 0) {
            for (String fileName : tslFiles) {
                cacheTSLFiles(assetsPath, fileName, destination);
            }
        }
    }

    private void cacheTSLFiles(String assetsPath, String fileName, String directory) {
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(getAssets().open(assetsPath + File.separator + fileName), StandardCharsets.UTF_8))) {
            FileUtils.createDirectoryIfNotExist(directory);
            FileUtils.writeToFile(reader, directory, fileName);
        } catch (IOException ex) {
            Timber.e(ex, "Failed to open file: %s", fileName);
        }
    }

    // StrictMode

    private void setupStrictMode() {
        if (BuildConfig.DEBUG) {
            StrictMode.enableDefaults();
        }
    }

    // BouncyCastle Security provider

    private void setupBouncyCastle() {
        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
        Security.addProvider(new BouncyCastleProvider());
    }

    // Timber

    private void setupTimber() {
        if (BuildConfig.DEBUG) {
            Timber.plant(new Timber.DebugTree());
        }
        // TODO error reporting
    }

    // ThreeTenAbp

    private void setupThreeTenAbp() {
        AndroidThreeTen.init(this);
    }

    // Container configuration

    private void setupSignLib() {
        SignLib.init(this, getString(R.string.main_settings_tsa_url_key), getConfigurationProvider(), UserAgentUtil.getUserAgent(getApplicationContext()));
    }

    private void setupRxJava() {
        RxJavaPlugins.setErrorHandler(throwable -> Timber.e(throwable, "RxJava error handler"));
    }

    private void setupConfiguration() {
        CachedConfigurationHandler cachedConfHandler = new CachedConfigurationHandler(getCacheDir());
        ConfigurationProperties confProperties = new ConfigurationProperties(getAssets());
        ConfigurationManager confManager = new ConfigurationManager(this, confProperties, cachedConfHandler, UserAgentUtil.getUserAgent(getApplicationContext()));

        // Initially load cached configuration (if it exists and default configuration is not newer) in a blocking (synchronous)
        // manner. If default conf is newer then load default conf (case where application was updated and cache was not removed,
        // then in new packaged APK the default might be newer than the previously cached conf).
        // Initial conf is load synchronously so there would be no state where asynchronous central configuration loading timed
        // out or not ready yet and application features not working due to missing configuration.
        if (cachedConfHandler.doesCachedConfigurationExist() && !isDefaultConfNewerThanCachedConf(confProperties, cachedConfHandler)) {
            configurationProvider = confManager.forceLoadCachedConfiguration();
        } else {
            configurationProvider = confManager.forceLoadDefaultConfiguration();
        }

        Bundle bundle = new Bundle();
        bundle.putParcelable(ConfigurationConstants.CONFIGURATION_PROVIDER, configurationProvider);
        ConfigurationProviderReceiver confProviderReceiver = new ConfigurationProviderReceiver(new Handler());
        confProviderReceiver.send(ConfigurationManagerService.NEW_CONFIGURATION_LOADED, bundle);

        // Load configuration again in asynchronous manner, from central if needed or cache if present.
//        initAsyncConfigurationLoad(new ConfigurationProviderReceiver(new Handler()), false);
    }

    private boolean isDefaultConfNewerThanCachedConf(ConfigurationProperties confProperties, CachedConfigurationHandler cachedConfHandler) {
        int defaultConfVersion = confProperties.getConfigurationVersionSerial();
        Integer cachedConfVersion = cachedConfHandler.getConfigurationVersionSerial();
        return cachedConfVersion == null || defaultConfVersion > cachedConfVersion;
    }

    // Following configuration updating should be asynchronous
    public void updateConfiguration(DiagnosticsView diagnosticsView) {
        ConfigurationProviderReceiver confProviderReceiver = new ConfigurationProviderReceiver(new Handler());
        confProviderReceiver.setDiagnosticView(diagnosticsView);
        initAsyncConfigurationLoad(confProviderReceiver, true);<commit_msg>Fix central configuration updating (#133)<commit_after>/*
 * Copyright 2017 <NAME>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

package ee.ria.DigiDoc.android;

import android.arch.lifecycle.ViewModel;
import android.arch.lifecycle.ViewModelProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
import android.os.StrictMode;
import android.support.annotation.NonNull;

import com.google.common.collect.ImmutableList;
import com.jakewharton.threetenabp.AndroidThreeTen;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.security.Security;
import java.util.Date;
import java.util.Map;

import javax.inject.Provider;
import javax.inject.Singleton;

import dagger.Binds;
import dagger.BindsInstance;
import dagger.Component;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
import ee.ria.DigiDoc.BuildConfig;
import ee.ria.DigiDoc.R;
import ee.ria.DigiDoc.android.crypto.create.CryptoCreateViewModel;
import ee.ria.DigiDoc.android.eid.EIDHomeViewModel;
import ee.ria.DigiDoc.android.main.diagnostics.DiagnosticsView;
import ee.ria.DigiDoc.android.main.home.HomeViewModel;
import ee.ria.DigiDoc.android.signature.create.SignatureCreateViewModel;
import ee.ria.DigiDoc.android.signature.data.SignatureContainerDataSource;
import ee.ria.DigiDoc.android.signature.data.source.FileSystemSignatureContainerDataSource;
import ee.ria.DigiDoc.android.signature.list.SignatureListViewModel;
import ee.ria.DigiDoc.android.signature.update.SignatureUpdateViewModel;
import ee.ria.DigiDoc.android.utils.Formatter;
import ee.ria.DigiDoc.android.utils.LocaleService;
import ee.ria.DigiDoc.android.utils.navigator.Navigator;
import ee.ria.DigiDoc.android.utils.navigator.conductor.ConductorNavigator;
import ee.ria.DigiDoc.configuration.util.UserAgentUtil;
import ee.ria.DigiDoc.configuration.ConfigurationConstants;
import ee.ria.DigiDoc.configuration.ConfigurationManager;
import ee.ria.DigiDoc.configuration.ConfigurationManagerService;
import ee.ria.DigiDoc.configuration.ConfigurationProperties;
import ee.ria.DigiDoc.configuration.ConfigurationProvider;
import ee.ria.DigiDoc.configuration.loader.CachedConfigurationHandler;
import ee.ria.DigiDoc.configuration.util.FileUtils;
import ee.ria.DigiDoc.crypto.RecipientRepository;
import ee.ria.DigiDoc.sign.SignLib;
import ee.ria.DigiDoc.smartcardreader.SmartCardReaderManager;
import ee.ria.DigiDoc.smartcardreader.acs.AcsSmartCardReader;
import ee.ria.DigiDoc.smartcardreader.identiv.IdentivSmartCardReader;
import io.reactivex.plugins.RxJavaPlugins;
import timber.log.Timber;

public class Application extends android.app.Application {

    private static ConfigurationProvider configurationProvider;

    @Override
    public void onCreate() {
        setupTSLFiles();
        setupStrictMode();
        super.onCreate();
        setupBouncyCastle();
        setupTimber();
        setupThreeTenAbp();
        setupConfiguration();
        setupRxJava();
        setupDagger();
    }

    // TSL files
    private void setupTSLFiles() {
        String destination = getCacheDir().toString() + "/schema";
        String assetsPath = "tslFiles";
        String[] tslFiles = null;
        try {
            tslFiles = getAssets().list(assetsPath);
        } catch (IOException e) {
            Timber.e(e, "Failed to get folder list: %s", assetsPath);
        }

        if (tslFiles != null && tslFiles.length > 0) {
            for (String fileName : tslFiles) {
                cacheTSLFiles(assetsPath, fileName, destination);
            }
        }
    }

    private void cacheTSLFiles(String assetsPath, String fileName, String directory) {
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(getAssets().open(assetsPath + File.separator + fileName), StandardCharsets.UTF_8))) {
            FileUtils.createDirectoryIfNotExist(directory);
            FileUtils.writeToFile(reader, directory, fileName);
        } catch (IOException ex) {
            Timber.e(ex, "Failed to open file: %s", fileName);
        }
    }

    // StrictMode

    private void setupStrictMode() {
        if (BuildConfig.DEBUG) {
            StrictMode.enableDefaults();
        }
    }

    // BouncyCastle Security provider

    private void setupBouncyCastle() {
        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
        Security.addProvider(new BouncyCastleProvider());
    }

    // Timber

    private void setupTimber() {
        if (BuildConfig.DEBUG) {
            Timber.plant(new Timber.DebugTree());
        }
        // TODO error reporting
    }

    // ThreeTenAbp

    private void setupThreeTenAbp() {
        AndroidThreeTen.init(this);
    }

    // Container configuration

    private void setupSignLib() {
        SignLib.init(this, getString(R.string.main_settings_tsa_url_key), getConfigurationProvider(), UserAgentUtil.getUserAgent(getApplicationContext()));
    }

    private void setupRxJava() {
        RxJavaPlugins.setErrorHandler(throwable -> Timber.e(throwable, "RxJava error handler"));
    }

    private void setupConfiguration() {
        CachedConfigurationHandler cachedConfHandler = new CachedConfigurationHandler(getCacheDir());
        ConfigurationProperties confProperties = new ConfigurationProperties(getAssets());
        ConfigurationManager confManager = new ConfigurationManager(this, confProperties, cachedConfHandler, UserAgentUtil.getUserAgent(getApplicationContext()));

        // Initially load cached configuration (if it exists and default configuration is not newer) in a blocking (synchronous)
        // manner. If default conf is newer then load default conf (case where application was updated and cache was not removed,
        // then in new packaged APK the default might be newer than the previously cached conf).
        // Initial conf is load synchronously so there would be no state where asynchronous central configuration loading timed
        // out or not ready yet and application features not working due to missing configuration.
        if (cachedConfHandler.doesCachedConfigurationExist() && !isDefaultConfNewerThanCachedConf(confProperties, cachedConfHandler)) {
            configurationProvider = confManager.forceLoadCachedConfiguration();
        } else {
            configurationProvider = confManager.forceLoadDefaultConfiguration();
        }

        Bundle bundle = new Bundle();
        bundle.putParcelable(ConfigurationConstants.CONFIGURATION_PROVIDER, configurationProvider);
        ConfigurationProviderReceiver confProviderReceiver = new ConfigurationProviderReceiver(new Handler());
        confProviderReceiver.send(ConfigurationManagerService.NEW_CONFIGURATION_LOADED, bundle);

        // Load configuration again in asynchronous manner, from central if needed or cache if present.
        initAsyncConfigurationLoad(new ConfigurationProviderReceiver(new Handler()), false);
    }

    private boolean isDefaultConfNewerThanCachedConf(ConfigurationProperties confProperties, CachedConfigurationHandler cachedConfHandler) {
        int defaultConfVersion = confProperties.getConfigurationVersionSerial();
        Integer cachedConfVersion = cachedConfHandler.getConfigurationVersionSerial();
        return cachedConfVersion == null || defaultConfVersion > cachedConfVersion;
    }

    // Following configuration updating should be asynchronous
    public void updateConfiguration(DiagnosticsView diagnosticsView) {
        ConfigurationProviderReceiver confProviderReceiver = new ConfigurationProviderReceiver(new Handler());
        confProviderReceiver.setDiagnosticView(diagnosticsView);
        initAsyncConfigurationLoad(confProviderReceiver, true);
{'fraction_non_alphanumeric': 0.08307674763370966, 'fraction_numerical': 0.002337780818793477, 'mean_word_length': 4.064683800173261, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3747037', 'n_tokens_mistral': 4693, 'n_tokens_neox': 4538, 'n_words': 1131}