/*
* Copyright (C) 2012-2016 The Android Money Manager Ex Project Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.money.manager.ex.currency;
import android.content.Context;
import android.database.Cursor;
//import net.sqlcipher.database.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import com.money.manager.ex.Constants;
import com.money.manager.ex.MoneyManagerApplication;
import com.money.manager.ex.R;
import com.money.manager.ex.core.FormatUtilities;
import com.money.manager.ex.core.InfoKeys;
import com.money.manager.ex.core.UIHelper;
import com.money.manager.ex.datalayer.AccountRepository;
import com.money.manager.ex.datalayer.CurrencyRepositorySql;
import com.money.manager.ex.datalayer.Select;
import com.money.manager.ex.investment.ISecurityPriceUpdater;
import com.money.manager.ex.servicelayer.AccountService;
import com.money.manager.ex.servicelayer.InfoService;
import com.money.manager.ex.domainmodel.Account;
import com.money.manager.ex.domainmodel.Currency;
import com.money.manager.ex.servicelayer.ServiceBase;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import javax.inject.Inject;
import info.javaperformance.money.Money;
import info.javaperformance.money.MoneyFactory;
import timber.log.Timber;
/**
* This class implements all the methods of utility for the management of currencies.
*/
public class CurrencyService
extends ServiceBase {
@Inject
public CurrencyService(Context context) {
super(context);
mCurrencyCodes = new HashMap<>();
mCurrencies = new SparseArray<>();
MoneyManagerApplication.getApp().iocComponent.inject(this);
}
@Inject CurrencyRepositorySql mRepository;
private Integer mBaseCurrencyId = null;
// hash map of all currencies
private SparseArray<Currency> mCurrencies;
/**
* a fast lookup for symbol -> id. i.e. EUR->2.
*/
private HashMap<String, Integer> mCurrencyCodes;
/**
* @param currencyId of the currency to be get
* @return a Currency. Null if fail
*/
public Currency getCurrency(Integer currencyId) {
if (currencyId == null || currencyId == Constants.NOT_SET) return null;
if (mCurrencies.indexOfKey(currencyId) >= 0) {
return mCurrencies.get(currencyId);
}
CurrencyRepository repository = getRepository();
Currency currency = repository.loadCurrency(currencyId);
mCurrencies.put(currencyId, currency);
return currency;
}
public Currency getCurrency(String currencyCode) {
int id = getIdForCode(currencyCode);
return getCurrency(id);
}
public String getBaseCurrencyCode() {
// get base currency
int baseCurrencyId = this.getBaseCurrencyId();
Currency currency = this.getCurrency(baseCurrencyId);
if (currency == null) {
// new UIHelper(getContext()).showToast(R.string.base_currency_not_set);
Timber.w(getContext().getString(R.string.base_currency_not_set));
return null;
}
return currency.getCode();
}
public String getSymbolFor(int id) {
Currency currency = getCurrency(id);
return currency.getCode();
}
public Integer getIdForCode(String code) {
if (mCurrencyCodes.containsKey(code)) {
return mCurrencyCodes.get(code);
}
CurrencyRepository repo = getRepository();
Currency currency = repo.loadCurrency(code);
mCurrencyCodes.put(code, currency.getCurrencyId());
Integer result = currency.getCurrencyId();
return result;
}
public List<Currency> getUsedCurrencies() {
AccountService service = new AccountService(getContext());
List<Account> accounts = service.getAccountList();
if (accounts == null) return null;
List<Currency> currencies = new ArrayList<>();
for (Account account : accounts) {
Currency currency = getCurrency(account.getCurrencyId());
if (!currencies.contains(currency)) {
currencies.add(currency);
}
}
// order by name?
Collections.sort(currencies, new Comparator<Currency>() {
@Override
public int compare(Currency lhs, Currency rhs) {
return lhs.getName().compareTo(rhs.getName());
}
});
return currencies;
}
public List<Currency> getUnusedCurrencies() {
List<Currency> usedCurrencies = getUsedCurrencies();
String usedList = "";
for (Currency currency : usedCurrencies) {
usedList += currency.getCurrencyId();
usedList += ", ";
}
// old trick. Now remove the last separator.
usedList = usedList.substring(0, usedList.length() - 2);
Select query = new Select(getRepository().getAllColumns())
.where(Currency.CURRENCYID + " NOT IN (" + usedList + ")")
.orderBy(Currency.CURRENCYNAME);
return getRepository().query(Currency.class, query);
}
public Money doCurrencyExchange(Integer toCurrencyId, Money amount, Integer fromCurrencyId) {
if (toCurrencyId == null || fromCurrencyId == null) return amount;
if (toCurrencyId == Constants.NOT_SET || fromCurrencyId == Constants.NOT_SET) return amount;
// e same currencies
if (toCurrencyId.equals(fromCurrencyId)) return amount;
Currency fromCurrencyFormats = getCurrency(fromCurrencyId);
Currency toCurrencyFormats = getCurrency(toCurrencyId);
// check if exists from and to currencies
if (fromCurrencyFormats == null || toCurrencyFormats == null) {
String message = fromCurrencyFormats == null
? "currency " + fromCurrencyId + " not loaded."
: "";
message += toCurrencyFormats == null
? " currency " + toCurrencyId + " not loaded."
: "";
throw new RuntimeException(message);
}
// exchange
double toConversionRate = toCurrencyFormats.getBaseConversionRate();
double fromConversionRate = fromCurrencyFormats.getBaseConversionRate();
// double result = (amount * fromConversionRate) / toConversionRate;
Money result = amount.multiply(fromConversionRate).divide(toConversionRate, Constants.DEFAULT_PRECISION);
return result;
}
/**
* Loads id of base currency.
*
* @return Id of base currency
*/
public int getBaseCurrencyId() {
if (mBaseCurrencyId != null) return mBaseCurrencyId;
int result;
Integer baseCurrencyId = loadBaseCurrencyId();
if (baseCurrencyId != null) {
result = baseCurrencyId;
} else {
// No base currency set yet. Try to get it from the system.
java.util.Currency systemCurrency = this.getSystemDefaultCurrency();
if (systemCurrency == null) {
// could not get base currency from the system. Use Euro?
//Currency euro = repo.loadCurrency("EUR");
//result = euro.getCurrencyId();
Log.w("CurrencyService", "system default currency is null!");
result = 2;
} else {
CurrencyRepository repo = getRepository();
Currency defaultCurrency = repo.loadCurrency(systemCurrency.getCurrencyCode());
if (defaultCurrency != null) {
result = defaultCurrency.getCurrencyId();
} else {
// currency not found.
Log.w("CurrencyService", "currency " + systemCurrency.getCurrencyCode() +
"not found!");
result = 2;
}
}
}
mBaseCurrencyId = result;
return result;
}
public void setBaseCurrencyId(int baseCurrencyId) {
mBaseCurrencyId = baseCurrencyId;
InfoService service = new InfoService(getContext());
boolean saved = service.setInfoValue(InfoKeys.BASECURRENCYID, Integer.toString(baseCurrencyId));
if (!saved) {
new UIHelper(getContext()).showToast(R.string.error_saving_default_currency);
}
}
public Currency getBaseCurrency() {
int baseCurrencyId = getBaseCurrencyId();
return getCurrency(baseCurrencyId);
}
/**
* Formats the given value, in base currency, as a string for display.
* @param value to format
* @return formatted value
*/
public String getBaseCurrencyFormatted(Money value) {
int baseCurrencyId = getBaseCurrencyId();
return this.getCurrencyFormatted(baseCurrencyId, value);
}
/**
* Format the given value with the currency preferences.
*
* @param currencyId of the currency to be formatted
* @param value value to format
* @return formatted value
*/
public String getCurrencyFormatted(Integer currencyId, Money value) {
String result;
// check if value is null
if (value == null) value = MoneyFactory.fromDouble(0);
// find currency id
if (currencyId != null) {
Currency currency = getCurrency(currencyId);
// NumericHelper helper = new NumericHelper(mContext);
if (currency == null) {
// no currency
return value.toString();
// we can not simply cut off the decimals!
// result = String.format("%.2f", value);
} else {
// formatted value
FormatUtilities formats = new FormatUtilities(getContext());
result = formats.format(value, currency);
}
} else {
result = String.valueOf(value);
}
return result;
}
private CurrencyRepository oldRepository;
public CurrencyRepository getRepository() {
if (oldRepository == null) {
oldRepository = new CurrencyRepository(getContext());
}
return oldRepository;
}
/**
* Import all currencies from Android System
*/
public boolean importCurrenciesFromSystemLocales() {
Locale[] locales = Locale.getAvailableLocales();
// get map codes and symbols
HashMap<String, String> symbols = getCurrenciesCodeAndSymbol();
java.util.Currency localeCurrency;
Currency newCurrency;
for (Locale locale : locales) {
try {
String country = locale.getCountry(); // ISO code
// String displayCountry = locale.getDisplayCountry(); // full country name
if (TextUtils.isEmpty(country)) continue;
localeCurrency = java.util.Currency.getInstance(locale);
// check if already exists currency symbol
if (mRepository.exists(localeCurrency.getCurrencyCode())) continue;
// No currency. Create a new one.
newCurrency = new Currency();
// Name
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
newCurrency.setName(localeCurrency.getDisplayName());
} else {
newCurrency.setName(localeCurrency.getSymbol());
}
// Symbol
newCurrency.setCode(localeCurrency.getCurrencyCode());
if (symbols != null && symbols.containsKey(localeCurrency.getCurrencyCode())) {
newCurrency.setPfxSymbol(symbols.get(localeCurrency.getCurrencyCode()));
}
newCurrency.setDecimalPoint(".");
newCurrency.setGroupSeparator(",");
int scale = (int) Math.pow((double)10, (double) localeCurrency.getDefaultFractionDigits());
newCurrency.setScale(scale);
newCurrency.setConversionRate(1.0);
getContext().getContentResolver().insert(getRepository().getUri(), newCurrency.contentValues);
//todo mRepository.insert(newCurrency.contentValues);
} catch (Exception e) {
Timber.e(e, "importing currencies from locale %s", locale.getDisplayName());
}
}
return true;
}
/**
* Checks if the currency is used in any of the accounts. Useful before deletion.
*
* @param currencyId Id of the currency to check.
* @return A boolean indicating if the currency is in use.
*/
public boolean isCurrencyUsed(int currencyId) {
AccountRepository accountRepository = new AccountRepository(getContext());
return accountRepository.anyAccountsUsingCurrency(currencyId);
}
/**
* Loads id of base currency
*
* @return Id base currency
*/
protected Integer loadBaseCurrencyId() {
InfoService infoService = new InfoService(getContext());
String currencyString = infoService.getInfoValue(InfoKeys.BASECURRENCYID);
Integer currencyId = null;
if (!TextUtils.isEmpty(currencyString)) {
currencyId = Integer.parseInt(currencyString);
}
return currencyId;
}
public java.util.Currency getSystemDefaultCurrency() {
java.util.Currency currency = null;
Locale defaultLocale = MoneyManagerApplication.getApp().getAppLocale();
try {
if (defaultLocale == null) {
defaultLocale = Locale.getDefault();
}
// Check if there is a country.
if (!TextUtils.isEmpty(defaultLocale.getCountry() )) {
currency = java.util.Currency.getInstance(defaultLocale);
}
// Otherwise no country info in the locale. Just use the default.
} catch (Exception ex) {
if (!(ex instanceof IllegalArgumentException)) {
String message = "getting default system currency";
if (defaultLocale != null) {
message += " for " + defaultLocale.getCountry();
}
Timber.e(ex, message);
}
// else, just ignore Currency parsing exception and use the pre-set currency below.
// http://docs.oracle.com/javase/7/docs/api/java/util/Currency.html#getInstance(java.util.Locale)
// IllegalArgumentException - if the country of the given locale is not a supported ISO 3166 country code.
}
// can't get the default currency?
if (currency == null) {
currency = java.util.Currency.getInstance(Locale.GERMANY);
}
return currency;
}
/**
* Fetches a currency by symbol.
* Ran on database creation during OpenHelper instantiation.
* todo Test if any advanced helpers can be used at that stage?
* @param db Database instance.
* @param currencySymbol Currency symbol to load.
* @return Id of the currency with the given symbol.
*/
public int loadCurrencyIdFromSymbolRaw(SQLiteDatabase db, String currencySymbol) {
int result = Constants.NOT_SET;
// Cannot use any other db source here as this happens on database creation!
Cursor cursor = db.query(CurrencyRepositorySql.TABLE_NAME,
new String[]{ Currency.CURRENCYID },
Currency.CURRENCY_SYMBOL + "=?",
new String[]{ currencySymbol },
null, null, null);
if (cursor == null) return result;
// set BaseCurrencyId
if (cursor.moveToFirst()) {
result = cursor.getInt(cursor.getColumnIndex(Currency.CURRENCYID));
}
cursor.close();
return result;
}
public boolean saveExchangeRate(String symbol, Money rate) {
CurrencyRepository repo = getRepository();
Currency currency = repo.loadCurrency(symbol);
int currencyId = currency.getCurrencyId();
// update value on database
int updateResult = repo.saveExchangeRate(currencyId, rate);
return updateResult > 0;
}
public void updateExchangeRate(int currencyId) {
List<Currency> currencies = new ArrayList<>();
currencies.add(getCurrency(currencyId));
updateExchangeRates(currencies);
}
public void updateExchangeRates(List<Currency> currencies){
if (currencies == null || currencies.size() <= 0) return;
ArrayList<String> currencySymbols = new ArrayList<>();
String symbol;
String baseCurrencySymbol = getBaseCurrencyCode();
for (Currency currency : currencies) {
symbol = currency.getCode();
if (symbol == null) continue;
if (symbol.equals(baseCurrencySymbol)) continue;
currencySymbols.add(symbol + baseCurrencySymbol + "=X");
}
ISecurityPriceUpdater updater = ExchangeRateUpdaterFactory.getUpdaterInstance(getContext());
updater.downloadPrices(currencySymbols);
// result received via event
}
// Private
/**
* @return a hash map of currency code / currency symbol
*/
private HashMap<String, String> getCurrenciesCodeAndSymbol() {
HashMap<String, String> map = new HashMap<>();
// compose map
String[] codes = getContext().getResources().getStringArray(R.array.currencies_code);
String[] symbols = getContext().getResources().getStringArray(R.array.currencies_symbol);
for (int i = 0; i < codes.length; i++) {
map.put(codes[i], symbols[i]);
}
return map;
}
}