diff --git a/DroidFish/res/xml/filepaths.xml b/DroidFish/res/xml/filepaths.xml
index b73a289..8097df4 100644
--- a/DroidFish/res/xml/filepaths.xml
+++ b/DroidFish/res/xml/filepaths.xml
@@ -1,3 +1,3 @@
-
+
diff --git a/DroidFish/src/org/petero/droidfish/DroidFish.java b/DroidFish/src/org/petero/droidfish/DroidFish.java
index fc0101d..7d93f3d 100644
--- a/DroidFish/src/org/petero/droidfish/DroidFish.java
+++ b/DroidFish/src/org/petero/droidfish/DroidFish.java
@@ -27,6 +27,7 @@ import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -225,6 +226,7 @@ public class DroidFish extends Activity
private ListView rightDrawer;
private SharedPreferences settings;
+ private ObjectCache cache;
private float scrollSensitivity;
private boolean invertScrollDirection;
@@ -475,6 +477,7 @@ public class DroidFish extends Activity
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
settings = PreferenceManager.getDefaultSharedPreferences(this);
+ cache = new ObjectCache();
setWakeLock(false);
@@ -506,7 +509,9 @@ public class DroidFish extends Activity
byte[] data = null;
int version = 1;
if (savedInstanceState != null) {
- data = savedInstanceState.getByteArray("gameState");
+ byte[] token = savedInstanceState.getByteArray("gameStateT");
+ if (token != null)
+ data = cache.retrieveBytes(token);
version = savedInstanceState.getInt("gameStateVersion", version);
} else {
String dataStr = settings.getString("gameState", null);
@@ -1125,7 +1130,8 @@ public class DroidFish extends Activity
super.onSaveInstanceState(outState);
if (ctrl != null) {
byte[] data = ctrl.toByteArray();
- outState.putByteArray("gameState", data);
+ byte[] token = data == null ? null : cache.storeBytes(data);
+ outState.putByteArray("gameStateT", token);
outState.putInt("gameStateVersion", 3);
}
}
@@ -1693,7 +1699,8 @@ public class DroidFish extends Activity
case RESULT_LOAD_PGN:
if (resultCode == RESULT_OK) {
try {
- String pgn = data.getAction();
+ String pgnToken = data.getAction();
+ String pgn = cache.retrieveString(pgnToken);
int modeNr = ctrl.getGameMode().getModeNr();
if ((modeNr != GameMode.ANALYSIS) && (modeNr != GameMode.EDIT_GAME))
newGameMode(GameMode.EDIT_GAME);
@@ -2337,7 +2344,29 @@ public class DroidFish extends Activity
Intent i = new Intent(Intent.ACTION_SEND);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
i.setType(game ? "application/x-chess-pgn" : "text/plain");
- i.putExtra(Intent.EXTRA_TEXT, ctrl.getPGN());
+ String pgn = ctrl.getPGN();
+ if (pgn.length() < 32768) {
+ i.putExtra(Intent.EXTRA_TEXT, pgn);
+ } else {
+ File dir = new File(getFilesDir(), "shared");
+ dir.mkdirs();
+ File file = new File(dir, game ? "game.pgn" : "game.txt");
+ try {
+ FileOutputStream fos = new FileOutputStream(file);
+ OutputStreamWriter ow = new OutputStreamWriter(fos, "UTF-8");
+ try {
+ ow.write(pgn);
+ } finally {
+ ow.close();
+ }
+ } catch (IOException e) {
+ Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG).show();
+ return;
+ }
+ String authority = "org.petero.droidfish.fileprovider";
+ Uri uri = FileProvider.getUriForFile(this, authority, file);
+ i.putExtra(Intent.EXTRA_STREAM, uri);
+ }
try {
startActivity(Intent.createChooser(i, getString(game ? R.string.share_game :
R.string.share_text)));
@@ -2351,7 +2380,7 @@ public class DroidFish extends Activity
Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
v.draw(c);
- File imgDir = new File(getFilesDir(), "images");
+ File imgDir = new File(getFilesDir(), "shared");
imgDir.mkdirs();
File file = new File(imgDir, "screenshot.png");
try {
@@ -3712,6 +3741,7 @@ public class DroidFish extends Activity
/** Save current game to a PGN file. */
private final void savePGNToFile(String pathName, boolean silent) {
String pgn = ctrl.getPGN();
+ String pgnToken = cache.storeString(pgn);
Editor editor = settings.edit();
editor.putString("currentPGNFile", pathName);
editor.putInt("currFT", FT_PGN);
@@ -3719,7 +3749,7 @@ public class DroidFish extends Activity
Intent i = new Intent(DroidFish.this, EditPGNSave.class);
i.setAction("org.petero.droidfish.saveFile");
i.putExtra("org.petero.droidfish.pathname", pathName);
- i.putExtra("org.petero.droidfish.pgn", pgn);
+ i.putExtra("org.petero.droidfish.pgn", pgnToken);
i.putExtra("org.petero.droidfish.silent", silent);
startActivity(i);
}
diff --git a/DroidFish/src/org/petero/droidfish/ObjectCache.java b/DroidFish/src/org/petero/droidfish/ObjectCache.java
new file mode 100644
index 0000000..1ea4774
--- /dev/null
+++ b/DroidFish/src/org/petero/droidfish/ObjectCache.java
@@ -0,0 +1,175 @@
+/*
+ DroidFish - An Android chess program.
+ Copyright (C) 2017 Peter Ă–sterlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package org.petero.droidfish;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.Arrays;
+
+import android.content.Context;
+
+/**
+ * Stores large objects temporarily in the file system to avoid
+ * too large transactions when communicating between activities.
+ * The cache has a limited size, so trying to retrieve a stored
+ * object can fail in which case null is returned. */
+public class ObjectCache {
+ public final static int MAX_MEM_SIZE = 16384; // Max size of object to store in memory
+ public final static int MAX_CACHED_OBJS = 10; // Max no of objects to cache in file system
+ private final Context context;
+
+ public ObjectCache() {
+ this(DroidFishApp.getContext());
+ }
+
+ public ObjectCache(Context context) {
+ this.context = context;
+ }
+
+ /** Store a string in the cache and return a token that can be
+ * used to retrieve the original string. */
+ public String storeString(String s) {
+ if (s.length() < MAX_MEM_SIZE) {
+ return "0" + s;
+ } else {
+ long token = storeInCache(s.getBytes());
+ return "1" + Long.toString(token);
+ }
+ }
+
+ /** Retrieve a string from the cache using a token previously
+ * returned by storeString().
+ * @return The string, or null if not found in the cache. */
+ public String retrieveString(String token) {
+ if (token.startsWith("0")) {
+ return token.substring(1);
+ } else {
+ String tokStr = token.substring(1);
+ long longTok = Long.valueOf(tokStr);
+ byte[] buf = retrieveFromCache(longTok);
+ return buf == null ? null : new String(buf);
+ }
+ }
+
+ /** Store a byte array in the cache and return a token that can be
+ * used to retrieve the original byte array. */
+ public byte[] storeBytes(byte[] b) {
+ if (b.length < MAX_MEM_SIZE) {
+ byte[] ret = new byte[b.length + 1];
+ ret[0] = 0;
+ System.arraycopy(b, 0, ret, 1, b.length);
+ return ret;
+ } else {
+ long token = storeInCache(b);
+ byte[] tokBuf = Long.toString(token).getBytes();
+ byte[] ret = new byte[1 + tokBuf.length];
+ ret[0] = 1;
+ System.arraycopy(tokBuf, 0, ret, 1, tokBuf.length);
+ return ret;
+ }
+ }
+
+ /** Retrieve a byte array from the cache using a token previously
+ * returned by storeBytes().
+ * @return The byte array, or null if not found in the cache. */
+ public byte[] retrieveBytes(byte[] token) {
+ if (token[0] == 0) {
+ byte[] ret = new byte[token.length - 1];
+ System.arraycopy(token, 1, ret, 0, token.length - 1);
+ return ret;
+ } else {
+ String tokStr = new String(token, 1, token.length - 1);
+ long longTok = Long.valueOf(tokStr);
+ return retrieveFromCache(longTok);
+ }
+ }
+
+ private final static String cacheDir = "objcache";
+
+ private long storeInCache(byte[] b) {
+ File cd = context.getCacheDir();
+ File dir = new File(cd, cacheDir);
+ if (dir.exists() || dir.mkdir()) {
+ try {
+ File[] files = dir.listFiles();
+ if (files != null) {
+ long[] tokens = new long[files.length];
+ long token = -1;
+ for (int i = 0; i < files.length; i++) {
+ try {
+ tokens[i] = Long.valueOf(files[i].getName());
+ token = Math.max(token, tokens[i]);
+ } catch (NumberFormatException nfe) {
+ }
+ }
+ Arrays.sort(tokens);
+ for (int i = 0; i < files.length - (MAX_CACHED_OBJS - 1); i++) {
+ File f = new File(dir, String.valueOf(tokens[i]));
+ f.delete();
+ }
+ int maxTries = 10;
+ for (int i = 0; i < maxTries; i++) {
+ token++;
+ File f = new File(dir, String.valueOf(token));
+ if (f.createNewFile()) {
+ FileOutputStream fos = new FileOutputStream(f);
+ try {
+ fos.write(b);
+ return token;
+ } finally {
+ fos.close();
+ }
+ }
+ }
+ }
+ } catch (IOException e) {
+ }
+ }
+ return -1;
+ }
+
+ private byte[] retrieveFromCache(long token) {
+ File cd = context.getCacheDir();
+ File dir = new File(cd, cacheDir);
+ if (dir.exists()) {
+ File f = new File(dir, String.valueOf(token));
+ try {
+ RandomAccessFile raf = new RandomAccessFile(f, "r");
+ try {
+ int len = (int)raf.length();
+ byte[] buf = new byte[len];
+ int offs = 0;
+ while (offs < len) {
+ int l = raf.read(buf, offs, len - offs);
+ if (l <= 0)
+ return null;
+ offs += l;
+ }
+ return buf;
+ } finally {
+ raf.close();
+ }
+ } catch (IOException ex) {
+ }
+ }
+ return null;
+ }
+}
diff --git a/DroidFish/src/org/petero/droidfish/activities/EditPGN.java b/DroidFish/src/org/petero/droidfish/activities/EditPGN.java
index 03a8e6c..dd276e2 100644
--- a/DroidFish/src/org/petero/droidfish/activities/EditPGN.java
+++ b/DroidFish/src/org/petero/droidfish/activities/EditPGN.java
@@ -23,6 +23,7 @@ import java.util.ArrayList;
import java.util.Locale;
import org.petero.droidfish.ColorTheme;
+import org.petero.droidfish.ObjectCache;
import org.petero.droidfish.R;
import org.petero.droidfish.Util;
import org.petero.droidfish.activities.PGNFile.GameInfo;
@@ -160,7 +161,8 @@ public class EditPGN extends ListActivity {
}
} else if (action.equals("org.petero.droidfish.saveFile")) {
loadGame = false;
- pgnToSave = i.getStringExtra("org.petero.droidfish.pgn");
+ String token = i.getStringExtra("org.petero.droidfish.pgn");
+ pgnToSave = (new ObjectCache()).retrieveString(token);
boolean silent = i.getBooleanExtra("org.petero.droidfish.silent", false);
if (silent) { // Silently append to file
PGNFile pgnFile2 = new PGNFile(fileName);
@@ -466,7 +468,8 @@ public class EditPGN extends ListActivity {
private final void sendBackResult(GameInfo gi) {
String pgn = pgnFile.readOneGame(gi);
if (pgn != null) {
- setResult(RESULT_OK, (new Intent()).setAction(pgn));
+ String pgnToken = (new ObjectCache()).storeString(pgn);
+ setResult(RESULT_OK, (new Intent()).setAction(pgnToken));
finish();
} else {
setResult(RESULT_CANCELED);
diff --git a/DroidFish/src/org/petero/droidfish/activities/LoadScid.java b/DroidFish/src/org/petero/droidfish/activities/LoadScid.java
index 82d68dd..43c077b 100644
--- a/DroidFish/src/org/petero/droidfish/activities/LoadScid.java
+++ b/DroidFish/src/org/petero/droidfish/activities/LoadScid.java
@@ -24,6 +24,7 @@ import java.util.Vector;
import java.util.concurrent.CountDownLatch;
import org.petero.droidfish.ColorTheme;
+import org.petero.droidfish.ObjectCache;
import org.petero.droidfish.R;
import org.petero.droidfish.Util;
@@ -371,7 +372,8 @@ public class LoadScid extends ListActivity {
if (cursor != null && cursor.moveToFirst()) {
String pgn = cursor.getString(cursor.getColumnIndex("pgn"));
if (pgn != null && pgn.length() > 0) {
- setResult(RESULT_OK, (new Intent()).setAction(pgn));
+ String pgnToken = (new ObjectCache()).storeString(pgn);
+ setResult(RESULT_OK, (new Intent()).setAction(pgnToken));
finish();
return;
}
diff --git a/DroidFishTest/src/org/petero/droidfish/ObjectCacheTest.java b/DroidFishTest/src/org/petero/droidfish/ObjectCacheTest.java
new file mode 100644
index 0000000..97252a5
--- /dev/null
+++ b/DroidFishTest/src/org/petero/droidfish/ObjectCacheTest.java
@@ -0,0 +1,133 @@
+/*
+ DroidFish - An Android chess program.
+ Copyright (C) 2017 Peter Ă–sterlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package org.petero.droidfish;
+
+
+import java.util.Arrays;
+
+import junit.framework.TestCase;
+
+
+public class ObjectCacheTest extends TestCase {
+ public ObjectCacheTest() {
+ }
+
+ public void testCache() {
+ ObjectCache cache = new ObjectCache(DroidFishApp.getContext());
+ final int M = ObjectCache.MAX_MEM_SIZE;
+ final int N = ObjectCache.MAX_CACHED_OBJS;
+ { // Test small string
+ String s0 = "testing";
+ String token = cache.storeString(s0);
+ String s = cache.retrieveString(token);
+ assertEquals(s0, s);
+ }
+ { // Test small byte array
+ byte[] b0 = {1,2,3,4,5};
+ byte[] token = cache.storeBytes(b0);
+ byte[] b = cache.retrieveBytes(token);
+ assertTrue(Arrays.equals(b0, b));
+ }
+ { // Test large string
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < M + 1; i++)
+ sb.append('a');
+ String s0 = sb.toString();
+ String token = cache.storeString(s0);
+ String s = cache.retrieveString(token);
+ assertEquals(s0, s);
+ }
+ { // Test large byte array
+ byte[] b0 = new byte[M+1];
+ for (int i = 0; i < M + 1; i++)
+ b0[i] = 'a';
+ byte[] token = cache.storeBytes(b0);
+ byte[] b = cache.retrieveBytes(token);
+ assertTrue(Arrays.equals(b0, b));
+ }
+ { // Test large string objects
+ String[] s0 = new String[N];
+ String[] tokens = new String[N];
+ for (int i = 0; i < N; i++) {
+ StringBuilder sb = new StringBuilder();
+ for (int j = 0; j < M + 1 + i * 100; j++)
+ sb.append((char)((i + j) % 255));
+ s0[i] = sb.toString();
+ tokens[i] = cache.storeString(s0[i]);
+ }
+ { // Small objects must not evict older entries
+ for (int i = 0; i < 100; i++)
+ cache.storeString("abc");
+ for (int i = 0; i < 100; i++)
+ cache.storeBytes(new byte[]{(byte)i,(byte)(i*2),(byte)(i+1)});
+ }
+ for (int i = 0; i < N; i++) {
+ String s = cache.retrieveString(tokens[i]);
+ assertEquals(s0[i], s);
+ }
+ }
+ { // Test large byte arrays
+ byte[][] b0 = new byte[N][];
+ byte[][] tokens = new byte[N][];
+ for (int i = 0; i < N; i++) {
+ byte[] b = new byte[M + 1 + i * 100];
+ for (int j = 0; j < b.length; j++)
+ b[j] = (byte)((i + j) % 255);
+ b0[i] = b;
+ tokens[i] = cache.storeBytes(b0[i]);
+ }
+ { // Small objects must not evict older entries
+ for (int i = 0; i < 100; i++)
+ cache.storeString("abc");
+ for (int i = 0; i < 100; i++)
+ cache.storeBytes(new byte[]{(byte)i,(byte)(i*2),(byte)(i+1)});
+ }
+ for (int i = 0; i < N; i++) {
+ byte[] b = cache.retrieveBytes(tokens[i]);
+ assertTrue(Arrays.equals(b0[i], b));
+ }
+ }
+
+ { // Test that not too many file system objects are used
+ String[] s0 = new String[N];
+ String[] tokens = new String[N];
+ for (int i = 0; i < N; i++) {
+ StringBuilder sb = new StringBuilder();
+ for (int j = 0; j < M + 1 + i * 100; j++)
+ sb.append((char)((i + j) % 255));
+ s0[i] = sb.toString();
+ tokens[i] = cache.storeString(s0[i]);
+ }
+ {
+ StringBuilder sb = new StringBuilder();
+ for (int j = 0; j < M + 1; j++)
+ sb.append((char)((j + 3) % 255));
+ String s = sb.toString();
+ String token = cache.storeString(s);
+ String s1 = cache.retrieveString(token);
+ assertEquals(s, s1);
+ }
+ assertEquals(null, cache.retrieveString(tokens[0]));
+ for (int i = 1; i < N; i++) {
+ String s = cache.retrieveString(tokens[i]);
+ assertEquals(s0[i], s);
+ }
+ }
+ }
+}