public class SaltEdgeSDK {

    private static SaltEdgeSDK instance;
    private Context applicationContext;
    private String appId;
    private String appSecret;
    private String returnToUrl;
    private boolean loggingEnabled;

    public static SaltEdgeSDK getInstance() {
        if (instance == null) {
            instance = new SaltEdgeSDK();
        }
        return instance;
    }

    public String getAppId() {
        return appId;
    }

    public String getAppSecret() {
        return appSecret;
    }

    public static String getReturnToUrl() {
        return getInstance().returnToUrl;
    }

    public static void setReturnToUrl(String returnToUrl) {
        getInstance().returnToUrl = returnToUrl;
    }

    public static boolean isLoggingEnabled() {
        return getInstance().loggingEnabled;
    }

    public Context getContext() {
        return applicationContext;
    }

    /**
     * Initialize Salt Edge SDK for access to Salt Edge API
     *
     * @param applicationContext Application context
     * @param clientAppId unique app id
     * @param clientAppSecret unique ap secret
     * @param returnToUrl the URL the user will be redirected to. The return_to URL should not exceed 2040 characters.
     */
    public void init(Context applicationContext,
                     String clientAppId,
                     String clientAppSecret,
                     String returnToUrl
    ) {
        init(applicationContext, clientAppId, clientAppSecret, returnToUrl, false);
    }

    /**
     * Initialize Salt Edge SDK for access to Salt Edge API
     *
     * @param applicationContext Application context
     * @param clientAppId unique app id
     * @param clientAppSecret unique ap secret
     * @param enableLogging flag, which enable or disable network logging
     * @param returnToUrl the URL the user will be redirected to. The return_to URL should not exceed 2040 characters.
     */
    public void init(
            Context applicationContext,
            String clientAppId,
            String clientAppSecret,
            String returnToUrl,
            boolean enableLogging
    ) {
        init(applicationContext, clientAppId, clientAppSecret, returnToUrl, false, enableLogging);
    }

    private void init(Context applicationContext,
                      String clientAppId,
                      String clientAppSecret,
                      String returnToUrl,
                      boolean actAsPartner,
                      boolean enableLogging
    ) {
        if (clientAppId.isEmpty()) {
            throw new RuntimeException(SEConstants.ERROR_CLIENT_APP_ID_IS_NULL);
        }
        if (clientAppSecret.isEmpty()) {
            throw new RuntimeException(SEConstants.ERROR_CLIENT_APP_SECRET_IS_NULL);
        }
        this.applicationContext = applicationContext;
        this.appId = clientAppId;
        this.appSecret = clientAppSecret;
        this.loggingEnabled = enableLogging;
        this.returnToUrl = returnToUrl;
    }

    public static void printToLogcat(String tag, String message) {
        if (isLoggingEnabled()) {
            Log.d(tag, message);
        }
    }
} public interface FetchAccountsResult {

    /**
     * Callback method is invoked when Fetch Accounts operation finished with success
     *
     * @param accounts List of SEAccount objects
     */
    void onSuccess(List<SEAccount> accounts);

    /**
     * Callback method is invoked when Fetch Accounts operation finished with error
     *
     * @param errorMessage String which describes occurred error
     */
    void onFailure(String errorMessage);
} public interface RefreshConnectionResult {

    /**
     * Callback method is invoked when Refresh Connection operation finished with success
     *
     * @param connection SEConnection objects
     */
    void onRefreshSuccess(SEConnection connection);

    /**
     * Callback method is invoked when Refresh Connection operation finished with error
     *
     * @param errorMessage String which describes occurred error
     */
    void onRefreshFailure(String errorMessage);

    /**
     * Callback method is invoked when interactive step of Refresh Connection operation finished with error
     *
     * @param errorMessage String which describes occurred error
     */
    void onInteractiveStepFailure(String errorMessage);

    /**
     * Callback method is invoked when Fetch State step of Refresh Connection operation finished with error
     *
     * @param errorMessage String which describes occurred error
     */
    void onConnectionStateFetchError(String errorMessage);

    /**
     * Callback method is invoked when the currently fetching Connection requires any interactive credentials for fetching.
     * Call `refreshService.sendInteractiveData(credentials)` when credentials are ready
     *
     * @param lastStage SEStage object
     */
    void provideInteractiveData(SEStage lastStage);
} public class SEAccount extends BaseModel {

    @SerializedName(SEConstants.KEY_NAME)
    private String name;

    @SerializedName(SEConstants.KEY_NATURE)
    private String nature;

    @SerializedName(SEConstants.KEY_BALANCE)
    private double balance;

    @SerializedName(SEConstants.KEY_CURRENCY_CODE)
    private String currencyCode;

    @SerializedName(SEConstants.KEY_EXTRA)
    private JSONObject extra;

// GETTER AND SETTERS

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNature() {
        return nature;
    }

    public void setNature(String nature) {
        this.nature = nature;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    public String getCurrencyCode() {
        return currencyCode;
    }

    public void setCurrencyCode(String currencyCode) {
        this.currencyCode = currencyCode;
    }

    public JSONObject getExtra() {
        return extra;
    }

    public void setExtra(JSONObject extra) {
        this.extra = extra;
    }
} public class SEConnection extends BaseModel implements Serializable {

    @SerializedName(SEConstants.KEY_SECRET)
    private String secret;

    @SerializedName(SEConstants.KEY_PROVIDER_ID)
    private String providerId;

    @SerializedName(SEConstants.KEY_PROVIDER_CODE)
    private String providerCode;

    @SerializedName(SEConstants.KEY_PROVIDER_NAME)
    private String providerName;

    @SerializedName(SEConstants.KEY_DAILY_REFRESH)
    private Boolean dailyRefresh;

    @SerializedName(SEConstants.KEY_LAST_SUCCESS_AT)
    private String lastSuccessAt;

    @SerializedName(SEConstants.KEY_STATUS)
    private String status;

    @SerializedName(SEConstants.KEY_COUNTRY_CODE)
    private String countryCode;

    @SerializedName(SEConstants.KEY_NEXT_REFRESH_POSSIBLE_AT)
    private String nextRefreshPossibleAt;

    @SerializedName(SEConstants.KEY_STORE_CREDENTIALS)
    private Boolean storeCredentials;

    @SerializedName(SEConstants.KEY_LAST_ATTEMPT)
    private SEAttempt lastAttempt;

    @SerializedName(SEConstants.KEY_HOLDER_INFO)
    private SEHolder holderInfo;

    @SerializedName(SEConstants.KEY_SHOW_CONSENT_CONFIRMATION)
    private Boolean showConsentConfirmation;

    @SerializedName(SEConstants.KEY_CONSENT_TYPES)
    private String[] consentTypes;

    @SerializedName(SEConstants.KEY_CONSENT_PERIOD_DAYS)
    private Integer consentPeriodDays;

    @SerializedName(SEConstants.KEY_CONSENT_GIVEN_AT)
    private String consentGivenAt;

    @SerializedName(SEConstants.KEY_CONSENT_EXPIRES_AT)
    private String consentExpiresAt;

    public boolean attemptIsFinished() {
        return lastAttempt != null && lastAttempt.getLastStage() != null
                && "finish".equals(lastAttempt.getLastStage().getName());
    }

    public boolean attemptIsInteractive() {
        return lastAttempt != null && lastAttempt.getLastStage() != null
                && "interactive".equals(lastAttempt.getLastStage().getName());
    }

    public Date getNextRefreshPossibleAtDate() {
        return SEDateTools.parseStringToDate(nextRefreshPossibleAt);
    }

// GETTER AND SETTERS

    public String getSecret() {
        return secret;
    }

    public void setSecret(String secret) {
        this.secret = secret;
    }

    public String getProviderId() {
        return providerId;
    }

    public void setProviderId(String providerId) {
        this.providerId = providerId;
    }

    public String getProviderCode() {
        return providerCode;
    }

    public void setProviderCode(String providerCode) {
        this.providerCode = providerCode;
    }

    public String getProviderName() {
        return providerName;
    }

    public void setProviderName(String providerName) {
        this.providerName = providerName;
    }

    public Boolean getDailyRefresh() {
        return dailyRefresh;
    }

    public void setDailyRefresh(Boolean dailyRefresh) {
        this.dailyRefresh = dailyRefresh;
    }

    public String getLastSuccessAt() {
        return lastSuccessAt;
    }

    public void setLastSuccessAt(String lastSuccessAt) {
        this.lastSuccessAt = lastSuccessAt;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getCountryCode() {
        return countryCode;
    }

    public void setCountryCode(String countryCode) {
        this.countryCode = countryCode;
    }

    public String getNextRefreshPossibleAt() {
        return nextRefreshPossibleAt;
    }

    public void setNextRefreshPossibleAt(String nextRefreshPossibleAt) {
        this.nextRefreshPossibleAt = nextRefreshPossibleAt;
    }

    public Boolean getStoreCredentials() {
        return storeCredentials;
    }

    public void setStoreCredentials(Boolean storeCredentials) {
        this.storeCredentials = storeCredentials;
    }

    public SEAttempt getLastAttempt() {
        return lastAttempt;
    }

    public void setLastAttempt(SEAttempt lastAttempt) {
        this.lastAttempt = lastAttempt;
    }

    public SEHolder getHolderInfo() {
        return holderInfo;
    }

    public void setHolderInfo(SEHolder holderInfo) {
        this.holderInfo = holderInfo;
    }

    public Boolean getShowConsentConfirmation() {
        return showConsentConfirmation;
    }

    public void setShowConsentConfirmation(Boolean showConsentConfirmation) {
        this.showConsentConfirmation = showConsentConfirmation;
    }

    public String[] getConsentTypes() {
        return consentTypes;
    }

    public void setConsentTypes(String[] consentTypes) {
        this.consentTypes = consentTypes;
    }

    public Integer getConsentPeriodDays() {
        return consentPeriodDays;
    }

    public void setConsentPeriodDays(Integer consentPeriodDays) {
        this.consentPeriodDays = consentPeriodDays;
    }

    public String getConsentGivenAt() {
        return consentGivenAt;
    }

    public void setConsentGivenAt(String consentGivenAt) {
        this.consentGivenAt = consentGivenAt;
    }

    public String getConsentExpiresAt() {
        return consentExpiresAt;
    }

    public void setConsentExpiresAt(String consentExpiresAt) {
        this.consentExpiresAt = consentExpiresAt;
    }
} public class SEStage extends BaseModel implements Serializable {

    @SerializedName(SEConstants.KEY_NAME)
    private String name;

    @SerializedName(SEConstants.KEY_INTERACTIVE_HTML)
    private String interactiveHtml;

    @SerializedName(SEConstants.KEY_INTERACTIVE_FIELDS_NAMES)
    private String[] interactiveFieldsNames;

    @SerializedName(SEConstants.KEY_INTERACTIVE_FIELDS_OPTIONS)
    private Map<String, InteractiveFieldOptionData> interactiveFieldsOptions;

// GETTER AND SETTERS

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getInteractiveHtml() {
        return interactiveHtml;
    }

    public void setInteractiveHtml(String interactiveHtml) {
        this.interactiveHtml = interactiveHtml;
    }

    public String[] getInteractiveFieldsNames() {
        return interactiveFieldsNames;
    }

    public void setInteractiveFieldsNames(String[] interactiveFieldsNames) {
        this.interactiveFieldsNames = interactiveFieldsNames;
    }

    public Map<String, InteractiveFieldOptionData> getInteractiveFieldsOptions() {
        return interactiveFieldsOptions;
    }

    public void setInteractiveFieldsOptions(Map<String, InteractiveFieldOptionData> interactiveFieldsOptions) {
        this.interactiveFieldsOptions = interactiveFieldsOptions;
    }
} public class SERefreshService {

    public SERefreshService(RefreshConnectionResult callback) {
        this.callback = callback;
    }

    private RefreshConnectionResult callback;
    private ConnectionRefreshConnector refreshConnector;
    private ConnectionInteractiveCredentialsConnector interactiveConnector;
    private ConnectionsConnector showConnectionConnector;
    private final static long POLLING_TIMEOUT = 5000L;
    private String customerSecret;
    private SEConnection connectionData;
    private Timer timer;
    private FetchConnectionResult refreshConnectionCallback = new FetchConnectionResult() {

        @Override
        public void onSuccess(SEConnection connection) {
            checkConnectionStage(connection);
        }

        @Override
        public void onFailure(String errorMessage) {
            onRefreshError(errorMessage);
        }
    };

    private FetchConnectionResult interactiveConnectionCallback = new FetchConnectionResult() {

        @Override
        public void onSuccess(SEConnection connection) {
            checkConnectionStage(connection);
        }

        @Override
        public void onFailure(String errorMessage) {
            onInteractiveStepError(errorMessage);
        }
    };

    private FetchConnectionsResult showConnectionsCallback = new FetchConnectionsResult() {

        @Override
        public void onSuccess(List<SEConnection> connections) {
            if (!connections.isEmpty()) {
                SEConnection lastConnectionData = connections.get(0);
                checkConnectionStage(lastConnectionData);
            }
        }

        @Override
        public void onFailure(String errorMessage) {
            if (callback != null) callback.onConnectionStateFetchError(errorMessage);
        }
    };

    /**
     * Start refresh connectionData process
     *
     * @param customerSecret - current customer secret
     * @param connectionData - ConnectionData model which should be refreshed
     * @param refreshScopes - refresh scopes. e.g. `['accounts', 'transactions']`
     * @return SERefreshService itself
     */
    public SERefreshService startRefresh(String customerSecret,
                                         SEConnection connectionData,
                                         String[] refreshScopes) {
        this.customerSecret = customerSecret;
        this.connectionData = connectionData;
        if (secretsNotValid()) {
            onRefreshError(SEConstants.ERROR_INVALID_REFRESH_SECRETS);
        } else {
            if (refreshConnector != null) {
                refreshConnector.cancel();
            }
            refreshConnector = new ConnectionRefreshConnector(refreshConnectionCallback);
            refreshConnector.refreshConnection(customerSecret, connectionData.getSecret(), refreshScopes);
        }
        return this;
    }

    /**
     * Cancel all refresh process
     */
    public void cancel() {
        if (refreshConnector != null) {
            refreshConnector.cancel();
        }
        if (interactiveConnector != null) {
            interactiveConnector.cancel();
        }
        stopConnectionPolling();
    }

    /**
     * Sends requested interactive credentials or empty map
     *
     * @param credentials - credentials map. e.g.: `{ "sms": "123456" }`
     */
    public void sendInteractiveData(Map<String, Object> credentials) {
        if (secretsNotValid()) {
            onRefreshError(SEConstants.ERROR_INVALID_REFRESH_SECRETS);
        } else {
            if (interactiveConnector != null) {
                interactiveConnector.cancel();
            }
            interactiveConnector = new ConnectionInteractiveCredentialsConnector(interactiveConnectionCallback);
            interactiveConnector.sendConnectionCredentials(
                    customerSecret,
                    connectionData.getSecret(),
                    credentials);
        }
    }

    private void startConnectionPolling() {
        try {
            timer = new Timer();
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    pollingAction();
                }
            }, 0, POLLING_TIMEOUT);
            showConnectionConnector = new ConnectionsConnector(showConnectionsCallback);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void stopConnectionPolling() {
        try {
            if (timer != null) {
                timer.cancel();
                timer.purge();
            }
            if (showConnectionConnector != null) {
                showConnectionConnector.cancel();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        showConnectionConnector = null;
        timer = null;
    }

    private boolean pollingIsStopped() {
        return timer == null;
    }

    private void pollingAction() {
        if (showConnectionConnector != null) {
            showConnectionConnector.fetchConnections(
                    customerSecret,
                    new String[]{ connectionData.getSecret() });
        }
    }

    private void checkConnectionStage(SEConnection connectionData) {
        if (connectionData.attemptIsFinished()) {
            onRefreshSuccess(connectionData);
        } else if (connectionData.attemptIsInteractive()) {
            askInteractiveData(connectionData.getLastAttempt().getLastStage());
        } else {
            if (pollingIsStopped()) {
                startConnectionPolling();
            }
        }
    }

    private void askInteractiveData(SEStage lastStage) {
        stopConnectionPolling();
        if (callback != null) callback.provideInteractiveData(lastStage);
    }

    private void onRefreshSuccess(SEConnection connectionData) {
        stopConnectionPolling();
        if (callback != null) callback.onRefreshSuccess(connectionData);
    }

    private void onRefreshError(String errorMessage) {
        stopConnectionPolling();
        if (callback != null) callback.onRefreshFailure(errorMessage);
    }

    private void onInteractiveStepError(String errorMessage) {
        if (callback != null) callback.onInteractiveStepFailure(errorMessage);
        if (pollingIsStopped()) {
            startConnectionPolling();
        }
    }

    private boolean secretsNotValid() {
        return customerSecret == null
                || customerSecret.isEmpty()
                || connectionData == null
                || connectionData.getSecret() == null
                || connectionData.getSecret().isEmpty();
    }
} public class SERequestManager {

    private static SERequestManager instance;

    public static SERequestManager getInstance() {
        if (instance == null) {
            instance = new SERequestManager();
        }
        return instance;
    }

    /**
     * Returns Currencies Rates objects collection for a desired date.
     *
     * Result is returned through callback (`onFetchCurrenciesSuccess(List<SECurrency> list)`).
     *
     * @param customerSecret [optional] current Customer secret code
     * @param ratesDate date for which collection of currencies rates should be returned
     * @param callback callback for request result
     */
    public void fetchCurrenciesRates(
            String customerSecret,
            String ratesDate,
            FetchCurrencyRatesResult callback
    ) {
        new CurrenciesConnector(callback).fetchCurrenciesRates(customerSecret, ratesDate);
    }

    /**
     * Allows to create new Customer.
     *
     * Result is returned through callback (`onCreateCustomerSuccess(String customerSecret)`).
     *
     * @param customerIdentifier a unique identifier of the new Customer
     * @param callback callback for request result
     */
    public void createCustomer(
            String customerIdentifier,
            CreateCustomerResult callback
    ) {
        if (TextUtils.isEmpty(customerIdentifier)) {
            throw new RuntimeException(SEConstants.ERROR_CUSTOMER_IDENTIFIER_IS_NULL);
        }
        new CustomerConnector(callback).createCustomer(customerIdentifier);
    }

    /**
     * Return list of available Providers.
     * Result is returned through callback.
     *
     * @param countryCode code of the provider’s country
     * @param callback callback for request result
     */
    public void fetchProviders(String countryCode, ProvidersResult callback) {
        new ProvidersConnector(callback).fetchProviders(countryCode);
    }

    /**
     * Allows you to create a connect session, which will be used to access Salt Edge Connect for Connection creation.
     * You will receive a connect_url, which allows you to enter directly to Salt Edge Connect with your newly generated connect session.
     * Result is returned through callback.
     *
     * @param customerSecret current Customer secret code
     * @param providerCode the code of the desired Provider
     * @param consentScopes fetching mode, possible values: ['holder_information], ['account_details'], ['transactions_details'] or combinations
     * @param localeCode the language of the Salt Edge Connect page in the ISO 639-1 format.
     * @param callback callback for request result
     */
    public void createConnectSession(
            String customerSecret,
            String providerCode,
            String[] consentScopes,
            String localeCode,
            ConnectSessionResult callback
    ) {
        new ConnectSessionConnector(callback).createConnectSession(
                customerSecret,
                providerCode,
                consentScopes,
                localeCode
        );
    }

    /**
     * Allows you to create a connect session, which will be used to access Salt Edge Connect for Connection creation.
     * You will receive a connect_url, which allows you to enter directly to Salt Edge Connect with your newly generated connect session.
     * Result is returned through callback.
     *
     * @param customerSecret current Customer secret code
     * @param dataMap custom params map
     * @param callback callback for request result
     */
    public void createConnectSession(
            String customerSecret,
            Map<String, Object> dataMap,
            ConnectSessionResult callback
    ) {
        new ConnectSessionConnector(callback).createConnectSession(customerSecret, dataMap);
    }

    /**
     * Allows you to create a connect session, which will be used to access Salt Edge Connect to reconnect a connection.
     * You will receive a connect_url, which allows you to enter directly to Salt Edge Connect with your newly generated connect session.
     * Result is returned through callback.
     *
     * @param customerSecret current Customer secret code
     * @param connectionSecret secret of the Connection which you want to reconnect
     * @param consentScopes fetching mode, possible values: ['holder_information], ['account_details'], ['transactions_details'] or combinations
     * @param localeCode the language of the Salt Edge Connect page in the ISO 639-1 format.
     * @param callback callback for request result
     */
    public void createReconnectSession(
            String customerSecret,
            String connectionSecret,
            String[] consentScopes,
            String localeCode,
            ConnectSessionResult callback
    ) {
        new ConnectSessionConnector(callback).createReconnectSession(
                customerSecret,
                connectionSecret,
                consentScopes,
                localeCode,
                false
        );
    }

    /**
     * Allows you to create a connect session, which will be used to access Salt Edge Connect to reconnect a connection.
     * You will receive a connect_url, which allows you to enter directly to Salt Edge Connect with your newly generated connect session.
     * Result is returned through callback.
     *
     * @param customerSecret current Customer secret code
     * @param connectionSecret secret of the Connection which you want to reconnect
     * @param consentScopes fetching mode, possible values: ['holder_information], ['account_details'], ['transactions_details'] or combinations
     * @param localeCode the language of the Salt Edge Connect page in the ISO 639-1 format.
     * @param overrideCredentials override credentials strategy. If true, the new credentials will automatically override the old ones.
     * @param callback callback for request result
     */
    public void createReconnectSession(
            String customerSecret,
            String connectionSecret,
            String[] consentScopes,
            String localeCode,
            boolean overrideCredentials,
            ConnectSessionResult callback
    ) {
        new ConnectSessionConnector(callback).createReconnectSession(
                customerSecret,
                connectionSecret,
                consentScopes,
                localeCode,
                overrideCredentials
        );
    }

    /**
     * Allows you to create a connect session, which will be used to access Salt Edge Connect to refresh a connection.
     * You will receive a connect_url, which allows you to enter directly to Salt Edge Connect with your newly generated connect session.
     * Result is returned through callback.
     *
     * @param customerSecret current Customer secret code
     * @param connectionSecret secret code of the Connection which you want to reconnect
     * @param localeCode the language of the Salt Edge Connect page in the ISO 639-1 format.
     * @param callback callback for request result
     */
    public void createRefreshSession(
            String customerSecret,
            String connectionSecret,
            String localeCode,
            ConnectSessionResult callback
    ) {
        new ConnectSessionConnector(callback).createRefreshSession(
                customerSecret,
                connectionSecret,
                localeCode
        );
    }

    /**
     * Created SERefreshService and starts refresh through Salt Edge API
     * Result is returned through callback.
     *
     * @param customerSecret current Customer secret code
     * @param connectionData Connection data for refresh
     * @param refreshScopes array of refresh scopes, e.g. {"accounts", "transactions"}
     * @param callback callback for request result
     * @return SERefreshService refresh service object
     */
    public SERefreshService refreshConnectionWithSecret(
            String customerSecret,
            SEConnection connectionData,
            String[] refreshScopes,
            RefreshConnectionResult callback
    ) {
        return new SERefreshService(callback).startRefresh(customerSecret, connectionData, refreshScopes);
    }

    /**
     * Used to create a connection for an OAuth provider.
     * Client will receive a redirect_url, which allows you to enter directly to Salt Edge Connect with your newly generated connect session.
     * All non-saltedge redirects should be opened in external app.
     * On finish the customer will be redirected to return_to URL.
     * Note that clients receive a connection_secret parameter in the return_to URL if the connection was successfully connected and an error_message parameter.
     *
     * Result is returned through callback.
     *
     * @param customerSecret current Customer secret code
     * @param providerCode the code of the desired Provider
     * @param consentScopes fetching mode, possible values: ['holder_information], ['account_details'], ['transactions_details'] or combinations
     * @param localeCode the language of the Salt Edge Connect page in the ISO 639-1 format.
     * @param callback callback for request result
     */
    public void createOAuthConnectSession(
            String customerSecret,
            String providerCode,
            String[] consentScopes,
            String localeCode,
            ConnectSessionResult callback
    ) {
        new ConnectOAuthConnector(callback).createConnectSession(
                customerSecret,
                providerCode,
                consentScopes,
                localeCode
        );
    }

    /**
     * Used to reconnect a connection for an OAuth provider
     * Client will receive a redirect_url, which allows you to enter directly to Salt Edge Connect with your newly generated connect session.
     * All non-saltedge redirects should be opened in external app.
     * On finish the customer will be redirected to return_to URL.
     * Note that clients receive a connection_secret parameter in the return_to URL if the connection was successfully connected and an error_message parameter.
     *
     * Result is returned through callback.
     *
     * @param customerSecret current Customer secret code
     * @param providerCode the code of the desired Provider
     * @param consentScopes fetching mode, possible values: ['holder_information], ['account_details'], ['transactions_details'] or combinations
     * @param localeCode the language of the Salt Edge Connect page in the ISO 639-1 format.
     * @param callback callback for request result
     */
    public void createOAuthReconnectSession(
            String customerSecret,
            String providerCode,
            String[] consentScopes,
            String localeCode,
            ConnectSessionResult callback
    ) {
        new ConnectOAuthConnector(callback).createConnectSession(
                customerSecret,
                providerCode,
                consentScopes,
                localeCode
        );
    }

    /**
     * Used to authorize a connection for an OAuth provider when using client owned provider keys.
     * @see [https://docs.saltedge.com/general/#client_provider_keys]
     *
     * Examples: `<return_to url>?access_token=bc4521d3&state=Pd8b4d0eb`, `<return_to url>#access_token=bc4521d3&state=Pd8b4d0eb`.
     * For both cases, the authorizeQuery that is needed to authorize the connection is `access_token=bc4521d3&state=Pd8b4d0eb`.
     *
     * Result is returned through callback.
     *
     * @param customerSecret current Customer secret code
     * @param connectionSecret secret code of the Connection which you want to reconnect
     * @param authorizeQuery authorization query string
     * @param callback callback for request result
     */
    public void authorizeOAuthConnection(
            String customerSecret,
            String connectionSecret,
            String authorizeQuery,
            FetchConnectionResult callback
    ) {
        new AuthorizeOAuthConnector(callback).authorizeConnection(
                customerSecret,
                connectionSecret,
                authorizeQuery
        );
    }

    /**
     * Allows you to create a lead session, which will be used to access Salt Edge Connect for Connection creation.
     * You will receive a redirect_url, which allows you to enter directly to Salt Edge Connect with your newly generated lead session.
     * Result is returned through callback.
     *
     * @param providerCode the code of the desired Provider
     * @param consentScopes fetching mode, possible values: ['holder_information], ['account_details'], ['transactions_details'] or combinations
     * @param localeCode the language of the Salt Edge Connect page in the ISO 639-1 format.
     * @param callback callback for request result
     */
    public void createLeadSession(
            String providerCode,
            String[] consentScopes,
            String localeCode,
            ConnectSessionResult callback
    ) {
        new LeadSessionConnector(callback).createLeadSession(
                providerCode,
                consentScopes,
                localeCode
        );
    }

    /**
     * Allows you to create a lead session, which will be used to access Salt Edge Connect for Connection creation.
     * You will receive a redirect_url, which allows you to enter directly to Salt Edge Connect with your newly generated lead session.
     * Result is returned through callback.
     *
     * @param dataMap custom params map
     * @param callback callback for request result
     */
    public void createLeadSession(
            Map<String, Object> dataMap,
            ConnectSessionResult callback
    ) {
        new LeadSessionConnector(callback).createLeadSession(dataMap);
    }

    /**
     * Returns a single Connection object.
     * Result is returned through callback.
     *
     * @param customerSecret current Customer secret code
     * @param connectionSecret secret code of the Connection which should be returned if exist
     * @param callback callback for request result
     */
    public void fetchConnection(
            String customerSecret,
            String connectionSecret,
            FetchConnectionsResult callback
    ) {
        String[] secrets = { connectionSecret };
        fetchConnections(customerSecret, secrets, callback);
    }

    /**
     * Returns Connections objects collection.
     * Result is returned through callback.
     *
     * @param customerSecret current Customer secret code
     * @param connectionsSecretsArray array of secrets of the Connections which should be returned if exist
     * @param callback callback for request result
     */
    public void fetchConnections(
            String customerSecret,
            String[] connectionsSecretsArray,
            FetchConnectionsResult callback) {
        new ConnectionsConnector(callback).fetchConnections(customerSecret, connectionsSecretsArray);
    }

    /**
     * Delete Connection.
     * Result is returned through callback.
     *
     * @param customerSecret current Customer secret code
     * @param connectionSecret secret code of the Connection which should be deleted if exist
     * @param callback callback for request result
     */
    public void deleteConnection(
            String customerSecret,
            String connectionSecret,
            DeleteEntryResult callback
    ) {
        new ConnectionDeleteConnector(callback).deleteConnection(customerSecret, connectionSecret);
    }

    /**
     * You can see the list of accounts of a Connection.
     * The accounts are sorted in ascending order of their ID, so the newest accounts will come last.
     * Result is returned through callback.
     *
     * @param customerSecret current Customer secret code
     * @param connectionSecret secret code of the Connection
     * @param callback callback for request result
     */
    public void fetchAccounts(
            String customerSecret,
            String connectionSecret,
            FetchAccountsResult callback
    ) {
        new AccountsConnector(callback).fetchAccounts(customerSecret, connectionSecret);
    }

    /**
     * Return the list of all non duplicated  transactions for an Account of a Connection.
     * The list not includes pending transactions
     * Result is returned through callback.
     *
     * @param customerSecret current Customer secret code
     * @param connectionSecret secret code of the Connection
     * @param accountId Account ID
     * @param callback callback for request result
     */
    public void fetchAllTransactions(
            String customerSecret,
            String connectionSecret,
            String accountId,
            FetchTransactionsResult callback
    ) {
        fetchTransactions(
                customerSecret,
                connectionSecret,
                accountId,
                "",
                false,
                true,
                callback
        );
    }

    /**
     * Return the list of all transactions from transactionId for an Account of a Connection.
     * The list not includes pending transactions
     * Result is returned through callback.
     *
     * @param customerSecret current Customer secret code
     * @param connectionSecret secret code of the Connection
     * @param accountId Account ID
     * @param fromTransactionId the id from which the result list should starts
     * @param callback callback for request result
     */
    public void fetchAllTransactions(
            String customerSecret,
            String connectionSecret,
            String accountId,
            String fromTransactionId,
            FetchTransactionsResult callback
    ) {
        fetchTransactions(
                customerSecret,
                connectionSecret,
                accountId,
                fromTransactionId,
                false,
                true,
                callback
        );
    }

    /**
     * Return the single page of transactions from transactionId for an Account of a Connection.
     * Transaction page contains maximum 100 items.
     * The list not includes pending transactions
     * Result is returned through callback.
     *
     * @param customerSecret current Customer secret code
     * @param connectionSecret secret code of the Connection
     * @param accountId Account ID
     * @param fromTransactionId the ID from which the next page of transactions starts
     * @param callback callback for request result
     */
    public void fetchTransactions(
            String customerSecret,
            String connectionSecret,
            String accountId,
            String fromTransactionId,
            FetchTransactionsResult callback
    ) {
        fetchTransactions(customerSecret,
                connectionSecret,
                accountId,
                fromTransactionId,
                false,
                false,
                callback);
    }

    /**
     * Return the list of transactions for an Account of a Connection.
     * Initiate transactions fetch by params.
     * The list includes posted or pending transactions.
     * Result is returned through callback.
     *
     * @param customerSecret current Customer secret code
     * @param connectionSecret secret code of the Connection
     * @param accountId Account ID
     * @param fromTransactionId the ID from which the next page of transactions starts
     * @param fetchPendingTransactions flag which indicates what type of transactions should be fetched (Normal or Pending)
     * @param fetchAllTransactionsFromId flag which indicates that should be fetched one page or all
     * @param callback callback for request result
     */
    public void fetchTransactions(
            String customerSecret,
            String connectionSecret,
            String accountId,
            String fromTransactionId,
            boolean fetchPendingTransactions,
            boolean fetchAllTransactionsFromId,
            FetchTransactionsResult callback
    ) {
        if (fetchPendingTransactions) {
            new PendingTransactionsFetchConnector(callback).fetchTransactions(
                    customerSecret,
                    connectionSecret,
                    accountId,
                    fromTransactionId,
                    fetchAllTransactionsFromId
            );
        } else {
            new TransactionsConnector(callback).fetchTransactions(
                    customerSecret,
                    connectionSecret,
                    accountId,
                    fromTransactionId,
                    fetchAllTransactionsFromId
            );
        }
    }

    /**
     * Return the list of all pending transactions for an Account of a Connection.
     * Result is returned through callback.
     *
     * @param customerSecret current Customer secret code
     * @param connectionSecret secret of the Connection
     * @param accountId Account ID
     * @param callback callback for request result
     */
    public void fetchPendingTransactionsOfAccount(
            String customerSecret,
            String connectionSecret,
            String accountId,
            FetchTransactionsResult callback
    ) {
        fetchPendingTransactionsOfAccount(
                customerSecret,
                connectionSecret,
                accountId,
                "",
                callback);
    }

    /**
     * Return the list of all pending transactions from transactionId for an Account of a Connection.
     * Result is returned through callback.
     *
     * @param customerSecret current Customer secret code
     * @param connectionSecret secret of the Connection
     * @param accountId Account ID
     * @param fromTransactionId the id from which the result list should starts
     * @param callback callback for request result
     */
    public void fetchPendingTransactionsOfAccount(
            String customerSecret,
            String connectionSecret,
            String accountId,
            String fromTransactionId,
            FetchTransactionsResult callback
    ) {
        new PendingTransactionsFetchConnector(callback).fetchTransactions(
                customerSecret,
                connectionSecret,
                accountId,
                fromTransactionId,
                true
        );
    }

    /**
     * Return the list of all duplicated transactions for an Account of a Connection.
     * Result is returned through callback.
     *
     * @param customerSecret current Customer secret code
     * @param connectionSecret secret of the Connection
     * @param accountId Account ID
     * @param callback callback for request result
     */
    public void fetchDuplicatedTransactionsOfAccount(
            String customerSecret,
            String connectionSecret,
            String accountId,
            FetchTransactionsResult callback
    ) {
        fetchDuplicatedTransactionsOfAccount(
                customerSecret,
                connectionSecret,
                accountId,
                "",
                callback);
    }

    /**
     * Return the list of all duplicated transactions from transactionId for an Account of a Connection.
     * Result is returned through callback.
     *
     * @param customerSecret current Customer secret code
     * @param connectionSecret secret of the Connection
     * @param accountId Account ID
     * @param fromTransactionId the id from which the result list should starts
     * @param callback callback for request result
     */
    public void fetchDuplicatedTransactionsOfAccount(
            String customerSecret,
            String connectionSecret,
            String accountId,
            String fromTransactionId,
            FetchTransactionsResult callback
    ) {
        new DuplicatedTransactionsFetchConnector(callback).fetchTransactions(
                customerSecret,
                connectionSecret,
                accountId,
                fromTransactionId,
                true
        );
    }

    /**
     * Mark a list of transactions as duplicated.
     * Result is returned through callback (`onUpdateTransactionsSuccess(Boolean success, "DUPLICATE")`)
     *
     * @param customerSecret current Customer secret code
     * @param connectionSecret secret of the Connection
     * @param transactionsIds the ids of transactions, that should be marked as duplicated
     * @param callback callback for request result
     */
    public void markTransactionsAsDuplicated(
            String customerSecret,
            String connectionSecret,
            List<String> transactionsIds,
            UpdateTransactionsResult callback
    ) {
        new TransactionsUpdateConnector(callback).markTransactionsAsDuplicated(
                customerSecret,
                connectionSecret,
                transactionsIds
        );
    }

    /**
     * Remove duplicated flag from a list of transactions.
     * Result is returned through callback (`onUpdateTransactionsSuccess(Boolean success, "UNDUPLICATE")`)
     *
     * @param customerSecret current Customer secret code
     * @param connectionSecret secret of the Connection
     * @param transactionsIds the ids of transactions, that should be marked as not-duplicated
     * @param callback callback for request result
     */
    public void markTransactionsAsNotDuplicated(
            String customerSecret,
            String connectionSecret,
            List<String> transactionsIds,
            UpdateTransactionsResult callback
    ) {
        new TransactionsUpdateConnector(callback).markTransactionsAsNotDuplicated(
                customerSecret,
                connectionSecret,
                transactionsIds
        );
    }

    /**
     * Remove transactions older than a specified period.
     * Result is returned through callback (`onTransactionsCleanupStartedSuccess(Boolean success)`)
     *
     * @param customerSecret current Customer secret code
     * @param connectionSecret secret of the Connection
     * @param accountId the id of the account
     * @param keepDays the amount of days for which the transactions will be kept. Transactions older than that will be irreversibly removed. Value should be greater than or equal to 60.
     * @param callback callback for request result
     */
    public void removeTransactions(
            String customerSecret,
            String connectionSecret,
            String accountId,
            int keepDays,
            UpdateTransactionsResult callback
    ) {
        new TransactionsUpdateConnector(callback).removeTransactions(
                customerSecret,
                connectionSecret,
                accountId,
                keepDays
        );
    }

    /**
     * You can see the list of consents of Connection.
     * The consents are sorted in ascending order of their ID, so the newest consents will come last.
     *
     * Result is returned through callback.
     *
     * @param customerSecret current Customer secret code
     * @param connectionSecret secret code of the Connection
     * @param callback callback for request result
     */
    public void fetchConsents(
            String customerSecret,
            String connectionSecret,
            FetchConsentsResult callback
    ) {
        new ConsentsConnector(callback).fetchConsents(customerSecret, connectionSecret);
    }

    /**
     * Consent revoke is an option that allows you to revoke a consent.
     * Result is returned through callback.
     *
     * @param customerSecret current Customer secret code
     * @param connectionSecret secret code of the Connection containing the consents
     * @param consentId unique ID of Consent which should be revoked
     * @param callback callback for request result
     */
    public void revokeConsent(
            String customerSecret,
            String connectionSecret,
            String consentId,
            DeleteEntryResult callback
    ) {
        new ConsentRevokeConnector(callback).revokeConsent(customerSecret, connectionSecret, consentId);
    }
} public class AccountAdapter extends ArrayAdapter<SEAccount> {

    public AccountAdapter(Context context, ArrayList<SEAccount> accounts) {
        super(context, 0, accounts);
    }

    @NonNull
    @Override
    public View getView(int position, View convertView, @NonNull ViewGroup parent) {
        SEAccount account = getItem(position);
        // Check if an existing view is being reused, otherwise inflate the view
        if (convertView == null) {
            convertView = LayoutInflater.from(getContext()).inflate(R.layout.list_item_account, parent, false);
        }
        // Lookup view for data population
        TextView title = convertView.findViewById(R.id.title);
        TextView subtitle = convertView.findViewById(R.id.subtitle);
        // Populate the data into the template view using the data object
        String titleText = "Invalid entry";
        String subtitleText = "";
        if (account != null) {
            titleText = account.getName();
            subtitleText = account.getBalance() + " " +account.getCurrencyCode();
        }
        title.setText(titleText);
        subtitle.setText(subtitleText);
        // Return the completed view to render on screen
        return convertView;
    }
} public class Constants {

    static String CONNECTIONS_SECRETS_ARRAY = "CONNECTIONS_SECRETS_ARRAY";
    public static final String KEY_CUSTOMER_SECRET = "secret";
    public static final String KEY_CUSTOMER_IDENTIFIER = "identifier";
    public static final String KEY_CONNECTION = "connection";
    public static final String KEY_CONNECTION_SECRET = "connection_secret";
    public static final String KEY_REFRESH = "refresh";
    public static final String KEY_OVERRIDE_CREDENTIALS = "override_credentials";
    public static final String KEY_OAUTH = "oauth";
    public static final String[] CONSENT_SCOPES = { SEConsent.SCOPE_ACCOUNT_DETAILS, SEConsent.SCOPE_TRANSACTIONS_DETAILS };
} public class PreferenceRepository {

    private static SharedPreferences sharedPreferences = null;

    public static void init(Context context) {
        sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());
    }

    public static void putStringToPreferences(String key, String value) {
        sharedPreferences.edit().putString(key, value).apply();
    }

    public static String getStringFromPreferences(String key) {
        return sharedPreferences.getString(key, "");
    }

    public static void removeRecordFromPreferences(String key) {
        sharedPreferences.edit().remove(key).apply();
    }

    public static void putConnectionSecret(String connectionSecret) {
        putStringToArrayPreferences(Constants.CONNECTIONS_SECRETS_ARRAY, connectionSecret);
    }

    public static String[] getAllConnectionsSecrets() {
        return getArrayFromPreferences(Constants.CONNECTIONS_SECRETS_ARRAY);
    }

    public static void removeConnectionSecret(String connectionSecret) {
        if (!connectionSecret.isEmpty()) {
            removeStringFromArrayPreferences(Constants.CONNECTIONS_SECRETS_ARRAY, connectionSecret);
        }
    }

    public static boolean connectionSecretIsSaved(String connectionSecret) {
        return !connectionSecret.isEmpty()
                && Arrays.asList(getAllConnectionsSecrets()).contains(connectionSecret);
    }

    private static void putStringToArrayPreferences(String arrayKey, String value) {
        ArrayList<String> list = new ArrayList<>(Arrays.asList(getArrayFromPreferences(arrayKey)));
        if (!list.contains(value)) list.add(value);
        sharedPreferences.edit().putString(arrayKey, new Gson().toJson(list)).apply();
    }

    private static void removeStringFromArrayPreferences(String arrayKey, String value) {
        ArrayList<String> list = new ArrayList<>(Arrays.asList(getArrayFromPreferences(arrayKey)));
        list.remove(value);
        sharedPreferences.edit().putString(arrayKey, new Gson().toJson(list)).apply();
    }

    private static String[] getArrayFromPreferences(String key) {
        String array = sharedPreferences.getString(key, "[]");
        return new Gson().fromJson(array, String[].class);
    }
} public class UITools {

    public static AlertDialog showAlertDialog(Context context, String message) {
        return showAlertDialog(context, message, null);
    }

    public static AlertDialog showAlertDialog(Context context, String message, DialogInterface.OnClickListener listener) {
        AlertDialog.Builder adb = new AlertDialog.Builder(context);
        adb.setIcon(android.R.drawable.ic_dialog_alert)
                .setTitle(R.string.warning)
                .setMessage(message)
                .setPositiveButton(android.R.string.ok, listener);
        return adb.show();
    }

    public static void destroyAlertDialog(AlertDialog dialog) {
        if (dialog != null) {
            dialog.dismiss();
        }
    }

    public static ProgressDialog refreshProgressDialog(Context context, ProgressDialog dialog, int messageResId) {
        return refreshProgressDialog(context, dialog, context.getString(messageResId));
    }

    public static ProgressDialog refreshProgressDialog(Context context, ProgressDialog dialog, String message) {
        destroyProgressDialog(dialog);
        dialog = new ProgressDialog(context);
        dialog.setMessage(message);
        dialog.setIndeterminate(true);
        dialog.show();
        return dialog;
    }

    public static ProgressDialog showProgressDialog(Context context, String message) {
        ProgressDialog dialog = new ProgressDialog(context);
        dialog.setMessage(message);
        dialog.setIndeterminate(true);
        dialog.show();
        return dialog;
    }

    public static void destroyProgressDialog(ProgressDialog dialog) {
        if (dialog != null) {
            dialog.dismiss();
        }
    }

    public static void hideProgress(ProgressDialog dialog) {
        dialog.hide();
    }

    public static void showShortToast(Context context, @StringRes Integer messageResId) {
        Toast.makeText(context, messageResId, Toast.LENGTH_SHORT).show();
    }

    public static void showLongToast(Context context, @StringRes Integer messageResId) {
        Toast.makeText(context, messageResId, Toast.LENGTH_LONG).show();
    }

    public static void showShortToast(Context context, String message) {
        Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
    }

    public static void showLongToast(Context context, String message) {
        Toast.makeText(context, message, Toast.LENGTH_LONG).show();
    }
} public static ProgressDialog refreshProgressDialog(Context context, ProgressDialog dialog, int messageResId) {
    return refreshProgressDialog(context, dialog, context.getString(messageResId));
}
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import com.saltedge.sdk.SaltEdgeSDK;
import com.saltedge.sdk.interfaces.FetchAccountsResult;
import com.saltedge.sdk.interfaces.RefreshConnectionResult;
import com.saltedge.sdk.model.SEAccount;
import com.saltedge.sdk.model.SEConnection;
import com.saltedge.sdk.model.SEStage;
import com.saltedge.sdk.network.SERefreshService;
import com.saltedge.sdk.network.SERequestManager;
import com.saltedge.sdk.sample.R;
import com.saltedge.sdk.sample.adapters.AccountAdapter;
import com.saltedge.sdk.sample.utils.Constants;
import com.saltedge.sdk.sample.utils.PreferenceRepository;
import com.saltedge.sdk.sample.utils.UITools;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import static com.saltedge.sdk.sample.utils.UITools.refreshProgressDialog;
/*
Copyright © 2019 Salt Edge. https://saltedge.com

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package com.saltedge.sdk.sample.features;






public class AccountsActivity extends AppCompatActivity implements AdapterView.OnItemClickListener,
        RefreshConnectionResult,
        InputValueResult
{
    private ProgressDialog progressDialog;
    private ArrayList<SEAccount> accounts;
    private SEConnection currentConnection;
    private ListView listView;
    private View emptyView;
    private TextView emptyLabelView;
    private MenuItem refreshMenuItem;
    private String customerSecret = "";
    private SERefreshService refreshService;
    private boolean fetchingAccounts = false;

    public static Intent newIntent(Activity activity, SEConnection connection) {
        Intent intent = new Intent(activity, AccountsActivity.class);
        intent.putExtra(Constants.KEY_CONNECTION, connection);
        return intent;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_list_view);
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            actionBar.setTitle(R.string.accounts);
            actionBar.setDisplayHomeAsUpEnabled(true);
        }
        setupViews();
        setInitialData();
    }

    @Override
    public void onStart() {
        super.onStart();
        fetchAccounts();
    }

    @Override
    protected void onStop() {
        UITools.destroyProgressDialog(progressDialog);