aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain/src/main/java/android/support/v4
Commit message (Expand)AuthorAgeFilesLines
* Remove unused FixedDrawerLayoutDominik Schürmann2015-02-231-49/+0
* Hack to disable overscroll effect of swipe to updateDominik Schürmann2014-09-131-0/+473
* Add license header to FixedDrawerLayoutDominik Schürmann2014-05-181-0/+16
* Workaround for nullpointer bug in support lib, close #591Dominik Schürmann2014-04-221-0/+33
='n39' href='#n39'>39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
/*
 * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
 *
 * 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 org.sufficientlysecure.keychain.util;

import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;

import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.KeychainApplication;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Iterator;

/**
 * When sending large data (over 1MB) through Androids Binder IPC you get
 * JavaBinder  E  !!! FAILED BINDER TRANSACTION !!!
 * <p/>
 * To overcome this problem, we cache large Parcelables into a file in our private cache directory
 * instead of sending them through IPC.
 */
public class ParcelableFileCache<E extends Parcelable> {

    private Context mContext;

    private final String mFilename;

    public ParcelableFileCache(Context context, String filename) {
        mContext = context;
        mFilename = filename;
    }

    public void writeCache(IteratorWithSize<E> it) throws IOException {
        writeCache(it.getSize(), it);
    }

    public void writeCache(int numEntries, Iterator<E> it) throws IOException {

        File cacheDir = mContext.getCacheDir();
        if (cacheDir == null) {
            // https://groups.google.com/forum/#!topic/android-developers/-694j87eXVU
            throw new IOException("cache dir is null!");
        }

        File tempFile = new File(mContext.getCacheDir(), mFilename);


        DataOutputStream oos = new DataOutputStream(new FileOutputStream(tempFile));

        try {
            oos.writeInt(numEntries);

            while (it.hasNext()) {
                Parcel p = Parcel.obtain(); // creating empty parcel object
                p.writeParcelable(it.next(), 0); // saving bundle as parcel
                byte[] buf = p.marshall();
                oos.writeInt(buf.length);
                oos.write(buf);
                p.recycle();
            }
        } finally {
            oos.close();
        }

    }

    /**
     * Reads from cache file and deletes it afterward. Convenience function for readCache(boolean).
     * @return an IteratorWithSize object containing entries read from the cache file
     * @throws IOException
     */
    public IteratorWithSize<E> readCache() throws IOException {
        return readCache(true);
    }

    /**
     * Reads entries from a cache file and returns an IteratorWithSize object containing the entries
     * @param deleteAfterRead if true, the cache file will be deleted after being read
     * @return an IteratorWithSize object containing entries read from the cache file
     * @throws IOException if cache directory/parcel import file does not exist, or a read error
     * occurs
     */
    public IteratorWithSize<E> readCache(final boolean deleteAfterRead) throws IOException {

        File cacheDir = mContext.getCacheDir();
        if (cacheDir == null) {
            // https://groups.google.com/forum/#!topic/android-developers/-694j87eXVU
            throw new IOException("cache dir is null!");
        }

        final File tempFile = new File(cacheDir, mFilename);
        final DataInputStream ois;
        try {
            ois = new DataInputStream(new FileInputStream(tempFile));
        } catch (FileNotFoundException e) {
            Log.e(Constants.TAG, "parcel import file not existing", e);
            throw new IOException(e);
        }

        final int numEntries = ois.readInt();

        return new IteratorWithSize<E>() {

            E mRing = null;
            boolean closed = false;
            byte[] buf = new byte[512];

            public int getSize() {
                return numEntries;
            }

            private void readNext() {
                if (mRing != null || closed) {
                    return;
                }

                try {

                    int length = ois.readInt();
                    while (buf.length < length) {
                        buf = new byte[buf.length * 2];
                    }
                    ois.readFully(buf, 0, length);

                    Parcel parcel = Parcel.obtain(); // creating empty parcel object
                    parcel.unmarshall(buf, 0, length);
                    parcel.setDataPosition(0);
                    mRing = parcel.readParcelable(KeychainApplication.class.getClassLoader());
                    parcel.recycle();
                } catch (EOFException e) {
                    // aight
                    close();
                } catch (IOException e) {
                    Log.e(Constants.TAG, "Encountered IOException during cache read!", e);
                }

            }

            @Override
            public boolean hasNext() {
                readNext();
                return mRing != null;
            }

            @Override
            public E next() {
                readNext();
                try {
                    return mRing;
                } finally {
                    mRing = null;
                }
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException();
            }

            @Override
            public void finalize() throws Throwable {
                close();
                super.finalize();
            }

            private void close() {
                if (!closed) {
                    try {
                        ois.close();
                        if (deleteAfterRead) {
                            //noinspection ResultOfMethodCallIgnored
                            tempFile.delete();
                        }
                    } catch (IOException e) {
                        // nvm
                    }
                }
                closed = true;
            }


        };
    }

    public boolean delete() throws IOException {

        File cacheDir = mContext.getCacheDir();
        if (cacheDir == null) {
            // https://groups.google.com/forum/#!topic/android-developers/-694j87eXVU
            throw new IOException("cache dir is null!");
        }

        final File tempFile = new File(cacheDir, mFilename);
        return tempFile.delete();
    }

    /** As the name implies, this is an extended iterator interface, which
     * knows the total number of its entries beforehand.
     */
    public static interface IteratorWithSize<E> extends Iterator<E> {
        /** Returns the number of total entries in this iterator. */
        int getSize();
    }

}