org.gnucash.android.ui.accounts.ExportDialogFragment.java Source code

Java tutorial

Introduction

Here is the source code for org.gnucash.android.ui.accounts.ExportDialogFragment.java

Source

/*
 * Copyright (c) 2012 Ngewi Fet <ngewif@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.gnucash.android.ui.accounts;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.nio.channels.FileChannel;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.gnucash.android.R;
import org.gnucash.android.ui.transactions.TransactionsDeleteConfirmationDialog;
import org.gnucash.android.util.OfxFormatter;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.ProcessingInstruction;

import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.Spinner;
import android.widget.Toast;

/**
 * Dialog fragment for exporting account information as OFX files.
 * @author Ngewi Fet <ngewif@gmail.com>
 */
public class ExportDialogFragment extends DialogFragment {

    /**
     * Spinner for selecting destination for the exported file.
     * The destination could either be SD card, or another application which
     * accepts files, like Google Drive.
     */
    Spinner mDestinationSpinner;

    /**
     * Checkbox indicating that all transactions should be exported,
     * regardless of whether they have been exported previously or not
     */
    CheckBox mExportAllCheckBox;

    /**
     * Checkbox for deleting all transactions after exporting them
     */
    CheckBox mDeleteAllCheckBox;

    /**
     * Save button for saving the exported files
     */
    Button mSaveButton;

    /**
     * Cancels the export dialog
     */
    Button mCancelButton;

    /**
     * File path for saving the OFX files
     */
    String mFilePath;

    /**
     * Click listener for positive button in the dialog.
     * @author Ngewi Fet <ngewif@gmail.com>
     */
    protected class ExportClickListener implements View.OnClickListener {

        @Override
        public void onClick(View v) {
            boolean exportAll = mExportAllCheckBox.isChecked();
            Document document = null;
            try {
                document = exportOfx(exportAll);
                writeToExternalStorage(document);
            } catch (Exception e) {
                Log.e(getTag(), e.getMessage());
                Toast.makeText(getActivity(), R.string.error_exporting, Toast.LENGTH_LONG).show();
                dismiss();
                return;
            }

            int position = mDestinationSpinner.getSelectedItemPosition();
            switch (position) {
            case 0:
                shareFile(mFilePath);
                break;

            case 1:
                File src = new File(mFilePath);
                new File(Environment.getExternalStorageDirectory() + "/gnucash/").mkdirs();
                File dst = new File(
                        Environment.getExternalStorageDirectory() + "/gnucash/" + buildExportFilename());

                try {
                    copyFile(src, dst);
                } catch (IOException e) {
                    Toast.makeText(getActivity(),
                            getString(R.string.toast_error_exporting_ofx) + dst.getAbsolutePath(),
                            Toast.LENGTH_LONG).show();
                    Log.e(getTag(), e.getMessage());
                    break;
                }

                //file already exists, just let the user know
                Toast.makeText(getActivity(), getString(R.string.toast_ofx_exported_to) + dst.getAbsolutePath(),
                        Toast.LENGTH_LONG).show();
                break;

            default:
                break;
            }

            if (mDeleteAllCheckBox.isChecked()) {
                Fragment currentFragment = getActivity().getSupportFragmentManager()
                        .findFragmentByTag(AccountsActivity.FRAGMENT_ACCOUNTS_LIST);
                TransactionsDeleteConfirmationDialog alertFragment = TransactionsDeleteConfirmationDialog
                        .newInstance(R.string.title_confirm_delete, 0);
                alertFragment.setTargetFragment(currentFragment, 0);
                alertFragment.show(getActivity().getSupportFragmentManager(),
                        "transactions_delete_confirmation_dialog");
            }

            dismiss();
        }

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.dialog_export_ofx, container, false);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        mFilePath = getActivity().getExternalFilesDir(null) + "/" + buildExportFilename();
        getDialog().setTitle(R.string.menu_export_ofx);
        bindViews();
    }

    /**
     * Collects references to the UI elements and binds click listeners
     */
    private void bindViews() {
        View v = getView();
        mDestinationSpinner = (Spinner) v.findViewById(R.id.spinner_export_destination);
        ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(getActivity(),
                R.array.export_destinations, android.R.layout.simple_spinner_item);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        mDestinationSpinner.setAdapter(adapter);

        SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
        mExportAllCheckBox = (CheckBox) v.findViewById(R.id.checkbox_export_all);
        mExportAllCheckBox
                .setChecked(sharedPrefs.getBoolean(getString(R.string.key_export_all_transactions), false));

        mDeleteAllCheckBox = (CheckBox) v.findViewById(R.id.checkbox_post_export_delete);
        mDeleteAllCheckBox.setChecked(
                sharedPrefs.getBoolean(getString(R.string.key_delete_transactions_after_export), false));

        mSaveButton = (Button) v.findViewById(R.id.btn_save);
        mSaveButton.setText(R.string.btn_export);
        mCancelButton = (Button) v.findViewById(R.id.btn_cancel);

        mCancelButton.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                dismiss();
            }
        });

        mSaveButton.setOnClickListener(new ExportClickListener());
    }

    /**
     * Writes the OFX document <code>doc</code> to external storage
     * @param Document containing OFX file data
     * @throws IOException if file could not be saved
     */
    private void writeToExternalStorage(Document doc) throws IOException {
        File file = new File(mFilePath);

        FileWriter writer = new FileWriter(file);
        write(doc, writer);

    }

    /**
     * Callback for when the activity chooser dialog is completed
     */
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        //TODO: fix the exception which is thrown on return
        if (resultCode == Activity.RESULT_OK) {
            //uploading or emailing has finished. clean up now.
            File file = new File(mFilePath);
            file.delete();
        }
    }

    /**
     * Starts an intent chooser to allow the user to select an activity to receive
     * the exported OFX file
     * @param path String path to the file on disk
     */
    private void shareFile(String path) {
        String defaultEmail = PreferenceManager.getDefaultSharedPreferences(getActivity())
                .getString(getString(R.string.key_default_export_email), null);
        Intent shareIntent = new Intent(Intent.ACTION_SEND);
        shareIntent.setType("application/xml");
        shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + path));
        shareIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.title_export_email));
        if (defaultEmail != null && defaultEmail.trim().length() > 0) {
            shareIntent.putExtra(Intent.EXTRA_EMAIL, new String[] { defaultEmail });
        }
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy.MM.dd HH:mm");
        shareIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.description_export_email)
                + formatter.format(new Date(System.currentTimeMillis())));
        startActivity(Intent.createChooser(shareIntent, getString(R.string.title_share_ofx_with)));
    }

    /**
     * Copies a file from <code>src</code> to <code>dst</code>
     * @param src Absolute path to the source file
     * @param dst Absolute path to the destination file 
     * @throws IOException if the file could not be copied
     */
    public static void copyFile(File src, File dst) throws IOException {
        //TODO: Make this asynchronous at some time, t in the future.
        FileChannel inChannel = new FileInputStream(src).getChannel();
        FileChannel outChannel = new FileOutputStream(dst).getChannel();
        try {
            inChannel.transferTo(0, inChannel.size(), outChannel);
        } finally {
            if (inChannel != null)
                inChannel.close();
            if (outChannel != null)
                outChannel.close();
        }
    }

    /**
     * Builds a file name based on the current time stamp for the exported file
     * @return String containing the file name
     */
    public static String buildExportFilename() {
        SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd_HHmm");
        String filename = formatter.format(new Date(System.currentTimeMillis())) + "_gnucash_all.ofx";
        return filename;
    }

    /**
     * Exports transactions in the database to the OFX format.
     * The accounts are written to a DOM document and returned
     * @param exportAll Flag to export all transactions or only the new ones since last export
     * @return DOM {@link Document} containing the OFX file information
     * @throws ParserConfigurationException
     */
    protected Document exportOfx(boolean exportAll) throws ParserConfigurationException {
        DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = docFactory.newDocumentBuilder();

        Document document = docBuilder.newDocument();
        Element root = document.createElement("OFX");

        ProcessingInstruction pi = document.createProcessingInstruction("OFX", OfxFormatter.OFX_HEADER);
        document.appendChild(pi);
        document.appendChild(root);

        OfxFormatter exporter = new OfxFormatter(getActivity(), exportAll);
        exporter.toOfx(document, root);

        return document;
    }

    /**
     * Writes out the file held in <code>document</code> to <code>outputWriter</code>
     * @param document {@link Document} containing the OFX document structure
     * @param outputWriter {@link Writer} to use in writing the file to stream
     */
    public void write(Document document, Writer outputWriter) {
        try {
            TransformerFactory transformerFactory = TransformerFactory.newInstance();
            Transformer transformer = transformerFactory.newTransformer();
            DOMSource source = new DOMSource(document);
            StreamResult result = new StreamResult(outputWriter);

            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");

            transformer.transform(source, result);
        } catch (TransformerConfigurationException txconfigException) {
            txconfigException.printStackTrace();
        } catch (TransformerException tfException) {
            tfException.printStackTrace();
        }
    }
}