/** * Converts all expenses into OFX XML format and adds them to the XML document * * @param doc DOM document of the OFX expenses. * @param parent Parent node for all expenses in report */ private void generateOfx(Document doc, Element parent) { Element transactionUid = doc.createElement(OfxHelper.TAG_TRANSACTION_UID); // unsolicited because the data exported is not as a result of a request transactionUid.appendChild(doc.createTextNode(OfxHelper.UNSOLICITED_TRANSACTION_ID)); Element statementTransactionResponse = doc.createElement(OfxHelper.TAG_STATEMENT_TRANSACTION_RESPONSE); statementTransactionResponse.appendChild(transactionUid); Element bankmsgs = doc.createElement(OfxHelper.TAG_BANK_MESSAGES_V1); bankmsgs.appendChild(statementTransactionResponse); parent.appendChild(bankmsgs); AccountsDbAdapter accountsDbAdapter = mAccountsDbAdapter; for (Account account : mAccountsList) { if (account.getTransactionCount() == 0) continue; // do not export imbalance accounts for OFX transactions and double-entry disabled if (!GnuCashApplication.isDoubleEntryEnabled() && account.getName().contains(mContext.getString(R.string.imbalance_account_name))) continue; // add account details (transactions) to the XML document account.toOfx(doc, statementTransactionResponse, mExportParams.getExportStartTime()); // mark as exported accountsDbAdapter.markAsExported(account.getUID()); } }
/** * Exports template accounts * * <p>Template accounts are just dummy accounts created for use with template transactions * * @param xmlSerializer XML serializer * @param accountList List of template accounts * @throws IOException if could not write XML to output stream */ private void exportTemplateAccounts(XmlSerializer xmlSerializer, Collection<Account> accountList) throws IOException { for (Account account : accountList) { xmlSerializer.startTag(null, GncXmlHelper.TAG_ACCOUNT); xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_VERSION, GncXmlHelper.BOOK_VERSION); // account name xmlSerializer.startTag(null, GncXmlHelper.TAG_NAME); xmlSerializer.text(account.getName()); xmlSerializer.endTag(null, GncXmlHelper.TAG_NAME); // account guid xmlSerializer.startTag(null, GncXmlHelper.TAG_ACCT_ID); xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_TYPE, GncXmlHelper.ATTR_VALUE_GUID); xmlSerializer.text(account.getUID()); xmlSerializer.endTag(null, GncXmlHelper.TAG_ACCT_ID); // account type xmlSerializer.startTag(null, GncXmlHelper.TAG_TYPE); xmlSerializer.text(account.getAccountType().name()); xmlSerializer.endTag(null, GncXmlHelper.TAG_TYPE); // commodity xmlSerializer.startTag(null, GncXmlHelper.TAG_ACCOUNT_COMMODITY); xmlSerializer.startTag(null, GncXmlHelper.TAG_COMMODITY_SPACE); xmlSerializer.text("template"); xmlSerializer.endTag(null, GncXmlHelper.TAG_COMMODITY_SPACE); xmlSerializer.startTag(null, GncXmlHelper.TAG_COMMODITY_ID); String acctCurrencyCode = "template"; xmlSerializer.text(acctCurrencyCode); xmlSerializer.endTag(null, GncXmlHelper.TAG_COMMODITY_ID); xmlSerializer.endTag(null, GncXmlHelper.TAG_ACCOUNT_COMMODITY); // commodity scu xmlSerializer.startTag(null, GncXmlHelper.TAG_COMMODITY_SCU); xmlSerializer.text("1"); xmlSerializer.endTag(null, GncXmlHelper.TAG_COMMODITY_SCU); if (account.getAccountType() != AccountType.ROOT && mRootTemplateAccount != null) { xmlSerializer.startTag(null, GncXmlHelper.TAG_PARENT_UID); xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_TYPE, GncXmlHelper.ATTR_VALUE_GUID); xmlSerializer.text(mRootTemplateAccount.getUID()); xmlSerializer.endTag(null, GncXmlHelper.TAG_PARENT_UID); } xmlSerializer.endTag(null, GncXmlHelper.TAG_ACCOUNT); } }
@Override public void endDocument() throws SAXException { super.endDocument(); HashMap<String, String> mapFullName = new HashMap<>(mAccountList.size()); HashMap<String, Account> mapImbalanceAccount = new HashMap<>(); // The XML has no ROOT, create one if (mRootAccount == null) { mRootAccount = new Account("ROOT"); mRootAccount.setAccountType(AccountType.ROOT); mAccountList.add(mRootAccount); mAccountMap.put(mRootAccount.getUID(), mRootAccount); } String imbalancePrefix = AccountsDbAdapter.getImbalanceAccountPrefix(); // Add all account without a parent to ROOT, and collect top level imbalance accounts for (Account account : mAccountList) { mapFullName.put(account.getUID(), null); boolean topLevel = false; if (account.getParentUID() == null && account.getAccountType() != AccountType.ROOT) { account.setParentUID(mRootAccount.getUID()); topLevel = true; } if (topLevel || (mRootAccount.getUID().equals(account.getParentUID()))) { if (account.getName().startsWith(imbalancePrefix)) { mapImbalanceAccount.put(account.getName().substring(imbalancePrefix.length()), account); } } } // Set the account for created balancing splits to correct imbalance accounts for (Split split : mAutoBalanceSplits) { String currencyCode = split.getAccountUID(); Account imbAccount = mapImbalanceAccount.get(currencyCode); if (imbAccount == null) { imbAccount = new Account(imbalancePrefix + currencyCode, Currency.getInstance(currencyCode)); imbAccount.setParentUID(mRootAccount.getUID()); imbAccount.setAccountType(AccountType.BANK); mapImbalanceAccount.put(currencyCode, imbAccount); mAccountList.add(imbAccount); } split.setAccountUID(imbAccount.getUID()); } java.util.Stack<Account> stack = new Stack<>(); for (Account account : mAccountList) { if (mapFullName.get(account.getUID()) != null) { continue; } stack.push(account); String parentAccountFullName; while (!stack.isEmpty()) { Account acc = stack.peek(); if (acc.getAccountType() == AccountType.ROOT) { // ROOT_ACCOUNT_FULL_NAME should ensure ROOT always sorts first mapFullName.put(acc.getUID(), AccountsDbAdapter.ROOT_ACCOUNT_FULL_NAME); stack.pop(); continue; } String parentUID = acc.getParentUID(); Account parentAccount = mAccountMap.get(parentUID); // ROOT account will be added if not exist, so now anly ROOT // has an empty parent if (parentAccount.getAccountType() == AccountType.ROOT) { // top level account, full name is the same as its name mapFullName.put(acc.getUID(), acc.getName()); stack.pop(); continue; } parentAccountFullName = mapFullName.get(parentUID); if (parentAccountFullName == null) { // non-top-level account, parent full name still unknown stack.push(parentAccount); continue; } mapFullName.put( acc.getUID(), parentAccountFullName + AccountsDbAdapter.ACCOUNT_NAME_SEPARATOR + acc.getName()); stack.pop(); } } for (Account account : mAccountList) { account.setFullName(mapFullName.get(account.getUID())); } long startTime = System.nanoTime(); mAccountsDbAdapter.beginTransaction(); Log.d(getClass().getSimpleName(), "bulk insert starts"); try { Log.d(getClass().getSimpleName(), "before clean up db"); mAccountsDbAdapter.deleteAllRecords(); Log.d( getClass().getSimpleName(), String.format("deb clean up done %d ns", System.nanoTime() - startTime)); long nAccounts = mAccountsDbAdapter.bulkAddRecords(mAccountList); Log.d("Handler:", String.format("%d accounts inserted", nAccounts)); // We need to add scheduled actions first because there is a foreign key constraint on // transactions // which are generated from scheduled actions (we do auto-create some transactions during // import) long nSchedActions = mScheduledActionsDbAdapter.bulkAddRecords(mScheduledActionsList); Log.d("Handler:", String.format("%d scheduled actions inserted", nSchedActions)); long nTempTransactions = mTransactionsDbAdapter.bulkAddRecords(mTemplateTransactions); Log.d("Handler:", String.format("%d template transactions inserted", nTempTransactions)); long nTransactions = mTransactionsDbAdapter.bulkAddRecords(mTransactionList); Log.d("Handler:", String.format("%d transactions inserted", nTransactions)); long nPrices = mPricesDbAdapter.bulkAddRecords(mPriceList); Log.d(getClass().getSimpleName(), String.format("%d prices inserted", nPrices)); long endTime = System.nanoTime(); Log.d(getClass().getSimpleName(), String.format("bulk insert time: %d", endTime - startTime)); mAccountsDbAdapter.setTransactionSuccessful(); } finally { mAccountsDbAdapter.endTransaction(); } }
@Override public void endElement(String uri, String localName, String qualifiedName) throws SAXException { String characterString = mContent.toString().trim(); if (mIgnoreElement != null) { // Ignore everything inside if (qualifiedName.equals(mIgnoreElement)) { mIgnoreElement = null; } mContent.setLength(0); return; } switch (qualifiedName) { case GncXmlHelper.TAG_NAME: mAccount.setName(characterString); mAccount.setFullName(characterString); break; case GncXmlHelper.TAG_ACCT_ID: mAccount.setUID(characterString); break; case GncXmlHelper.TAG_TYPE: AccountType accountType = AccountType.valueOf(characterString); mAccount.setAccountType(accountType); mAccount.setHidden(accountType == AccountType.ROOT); // flag root account as hidden break; case GncXmlHelper.TAG_COMMODITY_SPACE: if (characterString.equals("ISO4217")) { mISO4217Currency = true; } else { // price of non-ISO4217 commodities cannot be handled mPrice = null; } break; case GncXmlHelper.TAG_COMMODITY_ID: String currencyCode = mISO4217Currency ? characterString : NO_CURRENCY_CODE; if (mAccount != null) { mAccount.setCurrency(Currency.getInstance(currencyCode)); } if (mTransaction != null) { mTransaction.setCurrencyCode(currencyCode); } if (mPrice != null) { if (mPriceCommodity) { mPrice.setCommodityUID(mCommoditiesDbAdapter.getCommodityUID(currencyCode)); mPriceCommodity = false; } if (mPriceCurrency) { mPrice.setCurrencyUID(mCommoditiesDbAdapter.getCommodityUID(currencyCode)); mPriceCurrency = false; } } break; case GncXmlHelper.TAG_ACCT_DESCRIPTION: mAccount.setDescription(characterString); break; case GncXmlHelper.TAG_PARENT_UID: mAccount.setParentUID(characterString); break; case GncXmlHelper.TAG_ACCOUNT: if (!mInTemplates) { // we ignore template accounts, we have no use for them mAccountList.add(mAccount); mAccountMap.put(mAccount.getUID(), mAccount); // check ROOT account if (mAccount.getAccountType() == AccountType.ROOT) { if (mRootAccount == null) { mRootAccount = mAccount; } else { throw new SAXException("Multiple ROOT accounts exist in book"); } } // prepare for next input mAccount = null; // reset ISO 4217 flag for next account mISO4217Currency = false; } break; case GncXmlHelper.TAG_SLOT_KEY: switch (characterString) { case GncXmlHelper.KEY_PLACEHOLDER: mInPlaceHolderSlot = true; break; case GncXmlHelper.KEY_COLOR: mInColorSlot = true; break; case GncXmlHelper.KEY_FAVORITE: mInFavoriteSlot = true; break; case GncXmlHelper.KEY_NOTES: mIsNote = true; break; case GncXmlHelper.KEY_DEFAULT_TRANSFER_ACCOUNT: mInDefaultTransferAccount = true; break; case GncXmlHelper.KEY_EXPORTED: mInExported = true; break; case GncXmlHelper.KEY_SPLIT_ACCOUNT_SLOT: mInSplitAccountSlot = true; break; case GncXmlHelper.KEY_CREDIT_NUMERIC: mInCreditNumericSlot = true; break; case GncXmlHelper.KEY_DEBIT_NUMERIC: mInDebitNumericSlot = true; break; } break; case GncXmlHelper.TAG_SLOT_VALUE: if (mInPlaceHolderSlot) { // Log.v(LOG_TAG, "Setting account placeholder flag"); mAccount.setPlaceHolderFlag(Boolean.parseBoolean(characterString)); mInPlaceHolderSlot = false; } else if (mInColorSlot) { // Log.d(LOG_TAG, "Parsing color code: " + characterString); String color = characterString.trim(); // Gnucash exports the account color in format #rrrgggbbb, but we need only #rrggbb. // so we trim the last digit in each block, doesn't affect the color much if (!color.equals("Not Set")) { // avoid known exception, printStackTrace is very time consuming if (!Pattern.matches(Account.COLOR_HEX_REGEX, color)) color = "#" + color.replaceAll(".(.)?", "$1").replace("null", ""); try { if (mAccount != null) mAccount.setColorCode(color); } catch (IllegalArgumentException ex) { // sometimes the color entry in the account file is "Not set" instead of just blank. // So catch! Log.e( LOG_TAG, "Invalid color code '" + color + "' for account " + mAccount.getName()); Crashlytics.logException(ex); } } mInColorSlot = false; } else if (mInFavoriteSlot) { mAccount.setFavorite(Boolean.parseBoolean(characterString)); mInFavoriteSlot = false; } else if (mIsNote) { if (mTransaction != null) { mTransaction.setNote(characterString); mIsNote = false; } } else if (mInDefaultTransferAccount) { mAccount.setDefaultTransferAccountUID(characterString); mInDefaultTransferAccount = false; } else if (mInExported) { if (mTransaction != null) { mTransaction.setExported(Boolean.parseBoolean(characterString)); mInExported = false; } } else if (mInTemplates && mInSplitAccountSlot) { mSplit.setAccountUID(characterString); mInSplitAccountSlot = false; } else if (mInTemplates && mInCreditNumericSlot) { handleEndOfTemplateNumericSlot(characterString, TransactionType.CREDIT); } else if (mInTemplates && mInDebitNumericSlot) { handleEndOfTemplateNumericSlot(characterString, TransactionType.DEBIT); } break; // ================ PROCESSING OF TRANSACTION TAGS ===================================== case GncXmlHelper.TAG_TRX_ID: mTransaction.setUID(characterString); break; case GncXmlHelper.TAG_TRN_DESCRIPTION: mTransaction.setDescription(characterString); break; case GncXmlHelper.TAG_TS_DATE: try { if (mIsDatePosted && mTransaction != null) { mTransaction.setTime(GncXmlHelper.parseDate(characterString)); mIsDatePosted = false; } if (mIsDateEntered && mTransaction != null) { Timestamp timestamp = new Timestamp(GncXmlHelper.parseDate(characterString)); mTransaction.setCreatedTimestamp(timestamp); mIsDateEntered = false; } if (mPrice != null) { mPrice.setDate(new Timestamp(GncXmlHelper.parseDate(characterString))); } } catch (ParseException e) { Crashlytics.logException(e); String message = "Unable to parse transaction time - " + characterString; Log.e(LOG_TAG, message + "\n" + e.getMessage()); Crashlytics.log(message); throw new SAXException(message, e); } break; case GncXmlHelper.TAG_RECURRENCE_PERIOD: // for parsing of old backup files mRecurrencePeriod = Long.parseLong(characterString); mTransaction.setTemplate(mRecurrencePeriod > 0); break; case GncXmlHelper.TAG_SPLIT_ID: mSplit.setUID(characterString); break; case GncXmlHelper.TAG_SPLIT_MEMO: mSplit.setMemo(characterString); break; case GncXmlHelper.TAG_SPLIT_VALUE: try { // The value and quantity can have different sign for custom currency(stock). // Use the sign of value for split, as it would not be custom currency String q = characterString; if (q.charAt(0) == '-') { mNegativeQuantity = true; q = q.substring(1); } else { mNegativeQuantity = false; } mValue = GncXmlHelper.parseSplitAmount(characterString).abs(); // use sign from quantity } catch (ParseException e) { String msg = "Error parsing split quantity - " + characterString; Crashlytics.log(msg); Crashlytics.logException(e); throw new SAXException(msg, e); } break; case GncXmlHelper.TAG_SPLIT_QUANTITY: // delay the assignment of currency when the split account is seen try { mQuantity = GncXmlHelper.parseSplitAmount(characterString).abs(); } catch (ParseException e) { String msg = "Error parsing split quantity - " + characterString; Crashlytics.log(msg); Crashlytics.logException(e); throw new SAXException(msg, e); } break; case GncXmlHelper.TAG_SPLIT_ACCOUNT: if (!mInTemplates) { // this is intentional: GnuCash XML formats split amounts, credits are negative, debits // are positive. mSplit.setType(mNegativeQuantity ? TransactionType.CREDIT : TransactionType.DEBIT); // the split amount uses the account currency mSplit.setQuantity(new Money(mQuantity, getCurrencyForAccount(characterString))); // the split value uses the transaction currency mSplit.setValue(new Money(mValue, mTransaction.getCurrency())); mSplit.setAccountUID(characterString); } else { if (!mIgnoreTemplateTransaction) mTemplateAccountToTransactionMap.put(characterString, mTransaction.getUID()); } break; case GncXmlHelper.TAG_TRN_SPLIT: mTransaction.addSplit(mSplit); break; case GncXmlHelper.TAG_TRANSACTION: mTransaction.setTemplate(mInTemplates); Split imbSplit = mTransaction.getAutoBalanceSplit(); if (imbSplit != null) { mAutoBalanceSplits.add(imbSplit); } if (mInTemplates) { if (!mIgnoreTemplateTransaction) mTemplateTransactions.add(mTransaction); } else { mTransactionList.add(mTransaction); } if (mRecurrencePeriod > 0) { // if we find an old format recurrence period, parse it mTransaction.setTemplate(true); ScheduledAction scheduledAction = ScheduledAction.parseScheduledAction(mTransaction, mRecurrencePeriod); mScheduledActionsList.add(scheduledAction); } mRecurrencePeriod = 0; mIgnoreTemplateTransaction = true; mTransaction = null; break; case GncXmlHelper.TAG_TEMPLATE_TRANSACTIONS: mInTemplates = false; break; // ========================= PROCESSING SCHEDULED ACTIONS ================================== case GncXmlHelper.TAG_SX_ID: mScheduledAction.setUID(characterString); break; case GncXmlHelper.TAG_SX_NAME: if (characterString.equals(ScheduledAction.ActionType.BACKUP.name())) mScheduledAction.setActionType(ScheduledAction.ActionType.BACKUP); else mScheduledAction.setActionType(ScheduledAction.ActionType.TRANSACTION); break; case GncXmlHelper.TAG_SX_ENABLED: mScheduledAction.setEnabled(characterString.equals("y")); break; case GncXmlHelper.TAG_SX_AUTO_CREATE: mScheduledAction.setAutoCreate(characterString.equals("y")); break; case GncXmlHelper.TAG_SX_NUM_OCCUR: mScheduledAction.setTotalFrequency(Integer.parseInt(characterString)); break; case GncXmlHelper.TAG_RX_MULT: mRecurrenceMultiplier = Integer.parseInt(characterString); break; case GncXmlHelper.TAG_RX_PERIOD_TYPE: try { PeriodType periodType = PeriodType.valueOf(characterString.toUpperCase()); periodType.setMultiplier(mRecurrenceMultiplier); if (mScheduledAction != null) // there might be recurrence tags for bugdets and other stuff mScheduledAction.setPeriod(periodType); } catch (IllegalArgumentException ex) { // the period type constant is not supported String msg = "Unsupported period constant: " + characterString; Log.e(LOG_TAG, msg); Crashlytics.logException(ex); mIgnoreScheduledAction = true; } break; case GncXmlHelper.TAG_GDATE: try { long date = GncXmlHelper.DATE_FORMATTER.parse(characterString).getTime(); if (mIsScheduledStart && mScheduledAction != null) { mScheduledAction.setCreatedTimestamp(new Timestamp(date)); mIsScheduledStart = false; } if (mIsScheduledEnd && mScheduledAction != null) { mScheduledAction.setEndTime(date); mIsScheduledEnd = false; } if (mIsLastRun && mScheduledAction != null) { mScheduledAction.setLastRun(date); mIsLastRun = false; } if (mIsRecurrenceStart && mScheduledAction != null) { mScheduledAction.setStartTime(date); mIsRecurrenceStart = false; } } catch (ParseException e) { String msg = "Error parsing scheduled action date " + characterString; Log.e(LOG_TAG, msg + e.getMessage()); Crashlytics.log(msg); Crashlytics.logException(e); throw new SAXException(msg, e); } break; case GncXmlHelper.TAG_SX_TEMPL_ACCOUNT: if (mScheduledAction.getActionType() == ScheduledAction.ActionType.TRANSACTION) { mScheduledAction.setActionUID(mTemplateAccountToTransactionMap.get(characterString)); } else { mScheduledAction.setActionUID(UUID.randomUUID().toString().replaceAll("-", "")); } break; case GncXmlHelper.TAG_SCHEDULED_ACTION: if (mScheduledAction.getActionUID() != null && !mIgnoreScheduledAction) { mScheduledActionsList.add(mScheduledAction); int count = generateMissedScheduledTransactions(mScheduledAction); Log.i(LOG_TAG, String.format("Generated %d transactions from scheduled action", count)); } mRecurrenceMultiplier = 1; // reset it, even though it will be parsed from XML each time mIgnoreScheduledAction = false; break; // price table case GncXmlHelper.TAG_PRICE_ID: mPrice.setUID(characterString); break; case GncXmlHelper.TAG_PRICE_SOURCE: if (mPrice != null) { mPrice.setSource(characterString); } break; case GncXmlHelper.TAG_PRICE_VALUE: if (mPrice != null) { String[] parts = characterString.split("/"); if (parts.length != 2) { String message = "Illegal price - " + characterString; Log.e(LOG_TAG, message); Crashlytics.log(message); throw new SAXException(message); } else { mPrice.setValueNum(Long.valueOf(parts[0])); mPrice.setValueDenom(Long.valueOf(parts[1])); Log.d( getClass().getName(), "price " + characterString + " .. " + mPrice.getValueNum() + "/" + mPrice.getValueDenom()); } } break; case GncXmlHelper.TAG_PRICE_TYPE: if (mPrice != null) { mPrice.setType(characterString); } break; case GncXmlHelper.TAG_PRICE: if (mPrice != null) { mPriceList.add(mPrice); mPrice = null; } break; } // reset the accumulated characters mContent.setLength(0); }
/** * Serializes {@link ScheduledAction}s from the database to XML * * @param xmlSerializer XML serializer * @throws IOException */ private void exportScheduledTransactions(XmlSerializer xmlSerializer) throws IOException { // for now we will export only scheduled transactions to XML Cursor cursor = mScheduledActionDbAdapter.fetchAllRecords( ScheduledActionEntry.COLUMN_TYPE + "=?", new String[] {ScheduledAction.ActionType.TRANSACTION.name()}); while (cursor.moveToNext()) { String actionUID = cursor.getString(cursor.getColumnIndexOrThrow(ScheduledActionEntry.COLUMN_ACTION_UID)); Account accountUID = mTransactionToTemplateAccountMap.get(actionUID); if (accountUID == null) // if the action UID does not belong to a transaction we've seen before, skip it continue; xmlSerializer.startTag(null, GncXmlHelper.TAG_SCHEDULED_ACTION); xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_VERSION, GncXmlHelper.BOOK_VERSION); xmlSerializer.startTag(null, GncXmlHelper.TAG_SX_ID); String nameUID = accountUID.getName(); xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_TYPE, GncXmlHelper.ATTR_VALUE_GUID); xmlSerializer.text(nameUID); xmlSerializer.endTag(null, GncXmlHelper.TAG_SX_ID); xmlSerializer.startTag(null, GncXmlHelper.TAG_SX_NAME); ScheduledAction.ActionType actionType = ScheduledAction.ActionType.valueOf( cursor.getString(cursor.getColumnIndexOrThrow(ScheduledActionEntry.COLUMN_TYPE))); if (actionType == ScheduledAction.ActionType.TRANSACTION) { String description = TransactionsDbAdapter.getInstance() .getAttribute(actionUID, TransactionEntry.COLUMN_DESCRIPTION); xmlSerializer.text(description); } else { xmlSerializer.text(actionType.name()); } xmlSerializer.endTag(null, GncXmlHelper.TAG_SX_NAME); xmlSerializer.startTag(null, GncXmlHelper.TAG_SX_ENABLED); boolean enabled = cursor.getShort(cursor.getColumnIndexOrThrow(ScheduledActionEntry.COLUMN_ENABLED)) > 0; xmlSerializer.text(enabled ? "y" : "n"); xmlSerializer.endTag(null, GncXmlHelper.TAG_SX_ENABLED); xmlSerializer.startTag(null, GncXmlHelper.TAG_SX_AUTO_CREATE); xmlSerializer.text("n"); // we do not want transactions auto-created on the desktop. xmlSerializer.endTag(null, GncXmlHelper.TAG_SX_AUTO_CREATE); xmlSerializer.startTag(null, GncXmlHelper.TAG_SX_AUTO_CREATE_NOTIFY); xmlSerializer.text( "n"); // TODO: if we ever support notifying before creating a scheduled transaction, then // update this xmlSerializer.endTag(null, GncXmlHelper.TAG_SX_AUTO_CREATE_NOTIFY); xmlSerializer.startTag(null, GncXmlHelper.TAG_SX_ADVANCE_CREATE_DAYS); xmlSerializer.text("0"); xmlSerializer.endTag(null, GncXmlHelper.TAG_SX_ADVANCE_CREATE_DAYS); xmlSerializer.startTag(null, GncXmlHelper.TAG_SX_ADVANCE_REMIND_DAYS); xmlSerializer.text("0"); xmlSerializer.endTag(null, GncXmlHelper.TAG_SX_ADVANCE_REMIND_DAYS); xmlSerializer.startTag(null, GncXmlHelper.TAG_SX_INSTANCE_COUNT); String scheduledActionUID = cursor.getString(cursor.getColumnIndexOrThrow(ScheduledActionEntry.COLUMN_UID)); long instanceCount = mScheduledActionDbAdapter.getActionInstanceCount(scheduledActionUID); xmlSerializer.text(Long.toString(instanceCount)); xmlSerializer.endTag(null, GncXmlHelper.TAG_SX_INSTANCE_COUNT); // start date String createdTimestamp = cursor.getString(cursor.getColumnIndexOrThrow(ScheduledActionEntry.COLUMN_CREATED_AT)); long scheduleStartTime = Timestamp.valueOf(createdTimestamp).getTime(); serializeDate(xmlSerializer, GncXmlHelper.TAG_SX_START, scheduleStartTime); long lastRunTime = cursor.getLong(cursor.getColumnIndexOrThrow(ScheduledActionEntry.COLUMN_LAST_RUN)); if (lastRunTime > 0) { serializeDate(xmlSerializer, GncXmlHelper.TAG_SX_LAST, lastRunTime); } long endTime = cursor.getLong(cursor.getColumnIndexOrThrow(ScheduledActionEntry.COLUMN_END_TIME)); if (endTime > 0) { // end date serializeDate(xmlSerializer, GncXmlHelper.TAG_SX_END, endTime); } else { // add number of occurrences int totalFrequency = cursor.getInt( cursor.getColumnIndexOrThrow(ScheduledActionEntry.COLUMN_TOTAL_FREQUENCY)); xmlSerializer.startTag(null, GncXmlHelper.TAG_SX_NUM_OCCUR); xmlSerializer.text(Integer.toString(totalFrequency)); xmlSerializer.endTag(null, GncXmlHelper.TAG_SX_NUM_OCCUR); // remaining occurrences int executionCount = cursor.getInt( cursor.getColumnIndexOrThrow(ScheduledActionEntry.COLUMN_EXECUTION_COUNT)); xmlSerializer.startTag(null, GncXmlHelper.TAG_SX_REM_OCCUR); xmlSerializer.text(Integer.toString(totalFrequency - executionCount)); xmlSerializer.endTag(null, GncXmlHelper.TAG_SX_REM_OCCUR); } String tag = cursor.getString(cursor.getColumnIndexOrThrow(ScheduledActionEntry.COLUMN_TAG)); if (tag != null && !tag.isEmpty()) { xmlSerializer.startTag(null, GncXmlHelper.TAG_SX_TAG); xmlSerializer.text(tag); xmlSerializer.endTag(null, GncXmlHelper.TAG_SX_TAG); } xmlSerializer.startTag(null, GncXmlHelper.TAG_SX_TEMPL_ACCOUNT); xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_TYPE, GncXmlHelper.ATTR_VALUE_GUID); xmlSerializer.text(accountUID.getUID()); xmlSerializer.endTag(null, GncXmlHelper.TAG_SX_TEMPL_ACCOUNT); xmlSerializer.startTag(null, GncXmlHelper.TAG_SX_SCHEDULE); xmlSerializer.startTag(null, GncXmlHelper.TAG_RECURRENCE); xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_VERSION, GncXmlHelper.RECURRENCE_VERSION); long period = cursor.getLong(cursor.getColumnIndexOrThrow(ScheduledActionEntry.COLUMN_PERIOD)); PeriodType periodType = ScheduledAction.getPeriodType(period); xmlSerializer.startTag(null, GncXmlHelper.TAG_RX_MULT); xmlSerializer.text(String.valueOf(periodType.getMultiplier())); xmlSerializer.endTag(null, GncXmlHelper.TAG_RX_MULT); xmlSerializer.startTag(null, GncXmlHelper.TAG_RX_PERIOD_TYPE); xmlSerializer.text(periodType.name().toLowerCase()); xmlSerializer.endTag(null, GncXmlHelper.TAG_RX_PERIOD_TYPE); long recurrenceStartTime = cursor.getLong(cursor.getColumnIndexOrThrow(ScheduledActionEntry.COLUMN_START_TIME)); serializeDate(xmlSerializer, GncXmlHelper.TAG_RX_START, recurrenceStartTime); xmlSerializer.endTag(null, GncXmlHelper.TAG_RECURRENCE); xmlSerializer.endTag(null, GncXmlHelper.TAG_SX_SCHEDULE); xmlSerializer.endTag(null, GncXmlHelper.TAG_SCHEDULED_ACTION); } }