diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/ContentTypeIds.java b/MyTracks/src/com/google/android/apps/mytracks/content/ContentTypeIds.java index d1db14815..9df387f3d 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/content/ContentTypeIds.java +++ b/MyTracks/src/com/google/android/apps/mytracks/content/ContentTypeIds.java @@ -27,6 +27,7 @@ public class ContentTypeIds { public static final byte FLOAT_TYPE_ID = 3; public static final byte DOUBLE_TYPE_ID = 4; public static final byte STRING_TYPE_ID = 5; + public static final byte BLOB_TYPE_ID = 6; private ContentTypeIds() { /* Not instantiable */ } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupColumns.java b/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupColumns.java index 96de8cfa2..29bb2c869 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupColumns.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupColumns.java @@ -25,10 +25,11 @@ public class BackupColumns { public static final String[] POINTS_BACKUP_COLUMNS = { TrackPointsColumns._ID, TrackPointsColumns.TRACKID, TrackPointsColumns.LATITUDE, TrackPointsColumns.LONGITUDE, TrackPointsColumns.ALTITUDE, TrackPointsColumns.BEARING, - TrackPointsColumns.TIME, TrackPointsColumns.ACCURACY, TrackPointsColumns.SPEED }; + TrackPointsColumns.TIME, TrackPointsColumns.ACCURACY, TrackPointsColumns.SPEED, + TrackPointsColumns.SENSOR }; public static final byte[] POINTS_BACKUP_COLUMN_TYPES = { LONG_TYPE_ID, LONG_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID, FLOAT_TYPE_ID, - FLOAT_TYPE_ID, LONG_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID }; + FLOAT_TYPE_ID, LONG_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID, BLOB_TYPE_ID }; public static final String[] TRACKS_BACKUP_COLUMNS = { TracksColumns._ID, TracksColumns.NAME, TracksColumns.DESCRIPTION, TracksColumns.CATEGORY, diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/backup/DatabaseDumper.java b/MyTracks/src/com/google/android/apps/mytracks/io/backup/DatabaseDumper.java index 310a16824..59f35317f 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/backup/DatabaseDumper.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/backup/DatabaseDumper.java @@ -169,7 +169,7 @@ class DatabaseDumper { * Writes a single cell of the database to the output. * * @param columnIdx the column index to read from - * @param columnType the type of the column to be read + * @param columnTypeId the type of the column to be read * @param cursor the cursor to read from * @throws IOException if there are any errors while writing */ @@ -195,6 +195,12 @@ class DatabaseDumper { case ContentTypeIds.STRING_TYPE_ID: writer.writeUTF(cursor.getString(columnIdx)); return; + case ContentTypeIds.BLOB_TYPE_ID: { + byte[] blob = cursor.getBlob(columnIdx); + writer.writeInt(blob.length); + writer.write(blob); + return; + } default: throw new IllegalArgumentException( "Type " + columnTypeId + " not supported"); @@ -204,7 +210,7 @@ class DatabaseDumper { /** * Writes a dummy cell value to the output. * - * @param columnType the type of the value to write + * @param columnTypeId the type of the value to write * @throws IOException if there are any errors while writing */ private void writeDummyCell(byte columnTypeId, DataOutputStream writer) @@ -228,6 +234,9 @@ class DatabaseDumper { case ContentTypeIds.STRING_TYPE_ID: writer.writeUTF(""); return; + case ContentTypeIds.BLOB_TYPE_ID: + writer.writeInt(0); + return; default: throw new IllegalArgumentException( "Type " + columnTypeId + " not supported"); diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/backup/DatabaseImporter.java b/MyTracks/src/com/google/android/apps/mytracks/io/backup/DatabaseImporter.java index c57ce175c..21a8e7ccb 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/backup/DatabaseImporter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/backup/DatabaseImporter.java @@ -173,6 +173,23 @@ public class DatabaseImporter { if (values != null) { values.put(name, value); } return; } + case ContentTypeIds.BLOB_TYPE_ID: { + int blobLength = reader.readInt(); + if (blobLength != 0) { + byte[] blob = new byte[blobLength]; + int readBytes = reader.read(blob, 0, blobLength); + if (readBytes != blobLength) { + throw new IOException(String.format( + "Short read on column %s; expected %d bytes, read %d", + name, blobLength, readBytes)); + } + + if (values != null) { + values.put(name, blob); + } + } + return; + } default: throw new IOException("Read unknown type " + typeId); } diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/ApiFeatures.java b/MyTracks/src/com/google/android/apps/mytracks/util/ApiFeatures.java index 7f1016856..0bdec6e81 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/util/ApiFeatures.java +++ b/MyTracks/src/com/google/android/apps/mytracks/util/ApiFeatures.java @@ -34,6 +34,11 @@ public class ApiFeatures { public static final int ANDROID_API_LEVEL = Integer.parseInt( Build.VERSION.SDK); + private static final String PLATFORM_ADAPTER_GINGERBREAD = + "com.google.android.apps.mytracks.util.GingerbreadPlatformAdapter"; + private static final String PLATFORM_ADAPTER_ECLAIR = + "com.google.android.apps.mytracks.util.EclairPlatformAdapter"; + private static ApiFeatures instance; /** @@ -63,15 +68,28 @@ public class ApiFeatures { */ protected ApiFeatures() { if (getApiLevel() >= 9) { - apiPlatformAdapter = new GingerbreadPlatformAdapter(); + apiPlatformAdapter = createPlatformAdapter(PLATFORM_ADAPTER_GINGERBREAD); } else if (getApiLevel() >= 5) { - apiPlatformAdapter = new EclairPlatformAdapter(); + apiPlatformAdapter = createPlatformAdapter(PLATFORM_ADAPTER_ECLAIR); } else { - Log.i(MyTracksConstants.TAG, - "ApiFeatures: Using default platform adapter"); // Cupcake adapter is always supported, so it's safe to do static linkage. apiPlatformAdapter = new CupcakePlatformAdapter(); } + + Log.i(MyTracksConstants.TAG, "Using platform adapter " + apiPlatformAdapter.getClass()); + } + + private static ApiPlatformAdapter createPlatformAdapter(String className) { + try { + Class clazz = Class.forName(className); + return (ApiPlatformAdapter) clazz.newInstance(); + } catch (ClassNotFoundException e) { + throw new RuntimeException("ApiFeatures: Unable to find " + className, e); + } catch (InstantiationException e) { + throw new RuntimeException("ApiFeatures: Unable to instantiate " + className, e); + } catch (IllegalAccessException e) { + throw new RuntimeException("ApiFeatures: Unable to access " + className, e); + } } public ApiPlatformAdapter getApiPlatformAdapter() { diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/backup/DatabaseDumperTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/backup/DatabaseDumperTest.java index 4316ff3b4..d1615b8cf 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/io/backup/DatabaseDumperTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/backup/DatabaseDumperTest.java @@ -24,6 +24,7 @@ import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.io.InputStream; import junit.framework.TestCase; @@ -33,36 +34,51 @@ import junit.framework.TestCase; * @author Rodrigo Damazio */ public class DatabaseDumperTest extends TestCase { - + /** + * This class is the same as {@link MatrixCursor}, except this class + * implements {@link #getBlob} ({@link MatrixCursor} leaves it + * unimplemented). + */ + private class BlobAwareMatrixCursor extends MatrixCursor { + public BlobAwareMatrixCursor(String[] columnNames) { + super(columnNames); + } + + @Override public byte[] getBlob(int columnIndex) { + return getString(columnIndex).getBytes(); + } + } + private static final String[] COLUMN_NAMES = { - "intCol", "longCol", "floatCol", "doubleCol", "stringCol", "boolCol" + "intCol", "longCol", "floatCol", "doubleCol", "stringCol", "boolCol", "blobCol" }; private static final byte[] COLUMN_TYPES = { ContentTypeIds.INT_TYPE_ID, ContentTypeIds.LONG_TYPE_ID, ContentTypeIds.FLOAT_TYPE_ID, ContentTypeIds.DOUBLE_TYPE_ID, - ContentTypeIds.STRING_TYPE_ID, ContentTypeIds.BOOLEAN_TYPE_ID + ContentTypeIds.STRING_TYPE_ID, ContentTypeIds.BOOLEAN_TYPE_ID, + ContentTypeIds.BLOB_TYPE_ID }; private static final String[][] FAKE_DATA = { - { "42", "123456789", "3.1415", "2.72", "lolcat", "1" }, - { null, "123456789", "3.1415", "2.72", "lolcat", "1" }, - { "42", null, "3.1415", "2.72", "lolcat", "1" }, - { "42", "123456789", null, "2.72", "lolcat", "1" }, - { "42", "123456789", "3.1415", null, "lolcat", "1" }, - { "42", "123456789", "3.1415", "2.72", null, "1" }, - { "42", "123456789", "3.1415", "2.72", "lolcat", null }, + { "42", "123456789", "3.1415", "2.72", "lolcat", "1", "blob" }, + { null, "123456789", "3.1415", "2.72", "lolcat", "1", "blob" }, + { "42", null, "3.1415", "2.72", "lolcat", "1", "blob" }, + { "42", "123456789", null, "2.72", "lolcat", "1", "blob" }, + { "42", "123456789", "3.1415", null, "lolcat", "1", "blob" }, + { "42", "123456789", "3.1415", "2.72", null, "1", "blob" }, + { "42", "123456789", "3.1415", "2.72", "lolcat", null, "blob" }, + { "42", "123456789", "3.1415", "2.72", "lolcat", "1", null }, }; private static final long[] EXPECTED_FIELD_SETS = { - 0x3F, 0x3E, 0x3D, 0x3B, 0x37, 0x2F, 0x1F + 0x7F, 0x7E, 0x7D, 0x7B, 0x77, 0x6F, 0x5F, 0x3F }; - private MatrixCursor cursor; + private BlobAwareMatrixCursor cursor; @Override protected void setUp() throws Exception { super.setUp(); - // Add fake data to the cursor - cursor = new MatrixCursor(COLUMN_NAMES); + cursor = new BlobAwareMatrixCursor(COLUMN_NAMES); for (String[] row : FAKE_DATA) { cursor.addRow(row); } @@ -95,8 +111,9 @@ public class DatabaseDumperTest extends TestCase { assertEquals(FAKE_DATA.length, reader.readInt()); // Verify the row contents + byte[] blob = new byte[4]; - // Row 0 + // Row 0 -- everything populated assertEquals(EXPECTED_FIELD_SETS[0], reader.readLong()); assertEquals(42, reader.readInt()); assertEquals(123456789L, reader.readLong()); @@ -104,8 +121,10 @@ public class DatabaseDumperTest extends TestCase { assertEquals(2.72, reader.readDouble()); assertEquals("lolcat", reader.readUTF()); assertTrue(reader.readBoolean()); + assertEquals(4, reader.readInt()); + assertEquals("blob", readBlob(reader, 4)); - // Row 1 + // Row 1 -- no int assertEquals(EXPECTED_FIELD_SETS[1], reader.readLong()); if (hasNullFields) reader.readInt(); assertEquals(123456789L, reader.readLong()); @@ -113,8 +132,10 @@ public class DatabaseDumperTest extends TestCase { assertEquals(2.72, reader.readDouble()); assertEquals("lolcat", reader.readUTF()); assertTrue(reader.readBoolean()); + assertEquals(4, reader.readInt()); + assertEquals("blob", readBlob(reader, 4)); - // Row 2 + // Row 2 -- no long assertEquals(EXPECTED_FIELD_SETS[2], reader.readLong()); assertEquals(42, reader.readInt()); if (hasNullFields) reader.readLong(); @@ -122,8 +143,10 @@ public class DatabaseDumperTest extends TestCase { assertEquals(2.72, reader.readDouble()); assertEquals("lolcat", reader.readUTF()); assertTrue(reader.readBoolean()); + assertEquals(4, reader.readInt()); + assertEquals("blob", readBlob(reader, 4)); - // Row 3 + // Row 3 -- no float assertEquals(EXPECTED_FIELD_SETS[3], reader.readLong()); assertEquals(42, reader.readInt()); assertEquals(123456789L, reader.readLong()); @@ -131,8 +154,10 @@ public class DatabaseDumperTest extends TestCase { assertEquals(2.72, reader.readDouble()); assertEquals("lolcat", reader.readUTF()); assertTrue(reader.readBoolean()); + assertEquals(4, reader.readInt()); + assertEquals("blob", readBlob(reader, 4)); - // Row 4 + // Row 4 -- no double assertEquals(EXPECTED_FIELD_SETS[4], reader.readLong()); assertEquals(42, reader.readInt()); assertEquals(123456789L, reader.readLong()); @@ -140,8 +165,10 @@ public class DatabaseDumperTest extends TestCase { if (hasNullFields) reader.readDouble(); assertEquals("lolcat", reader.readUTF()); assertTrue(reader.readBoolean()); + assertEquals(4, reader.readInt()); + assertEquals("blob", readBlob(reader, 4)); - // Row 5 + // Row 5 -- no string assertEquals(EXPECTED_FIELD_SETS[5], reader.readLong()); assertEquals(42, reader.readInt()); assertEquals(123456789L, reader.readLong()); @@ -149,8 +176,10 @@ public class DatabaseDumperTest extends TestCase { assertEquals(2.72, reader.readDouble()); if (hasNullFields) reader.readUTF(); assertTrue(reader.readBoolean()); + assertEquals(4, reader.readInt()); + assertEquals("blob", readBlob(reader, 4)); - // Row 6 + // Row 6 -- no boolean assertEquals(EXPECTED_FIELD_SETS[6], reader.readLong()); assertEquals(42, reader.readInt()); assertEquals(123456789L, reader.readLong()); @@ -158,8 +187,23 @@ public class DatabaseDumperTest extends TestCase { assertEquals(2.72, reader.readDouble()); assertEquals("lolcat", reader.readUTF()); if (hasNullFields) reader.readBoolean(); - } + assertEquals(4, reader.readInt()); + assertEquals("blob", readBlob(reader, 4)); + // Row 7 -- no blob + assertEquals(EXPECTED_FIELD_SETS[7], reader.readLong()); + assertEquals(42, reader.readInt()); + assertEquals(123456789L, reader.readLong()); + assertEquals(3.1415f, reader.readFloat()); + assertEquals(2.72, reader.readDouble()); + assertEquals("lolcat", reader.readUTF()); + assertTrue(reader.readBoolean()); + if (hasNullFields) { + int length = reader.readInt(); + readBlob(reader, length); + } + } + public void testFewerRows() throws Exception { // Dump only the first two rows DatabaseDumper dumper = new DatabaseDumper(COLUMN_NAMES, COLUMN_TYPES, false); @@ -191,6 +235,8 @@ public class DatabaseDumperTest extends TestCase { assertEquals(2.72, reader.readDouble()); assertEquals("lolcat", reader.readUTF()); assertTrue(reader.readBoolean()); + assertEquals(4, reader.readInt()); + assertEquals("blob", readBlob(reader, 4)); // Row 1 assertEquals(EXPECTED_FIELD_SETS[1], reader.readLong()); @@ -200,13 +246,25 @@ public class DatabaseDumperTest extends TestCase { assertEquals(2.72, reader.readDouble()); assertEquals("lolcat", reader.readUTF()); assertTrue(reader.readBoolean()); + assertEquals(4, reader.readInt()); + assertEquals("blob", readBlob(reader, 4)); } private void assertHeader(DataInputStream reader) throws IOException { - assertEquals(6, reader.readInt()); + assertEquals(COLUMN_NAMES.length, reader.readInt()); for (int i = 0; i < COLUMN_NAMES.length; i++) { assertEquals(COLUMN_NAMES[i], reader.readUTF()); assertEquals(COLUMN_TYPES[i], reader.readByte()); } } + + private String readBlob(InputStream reader, int length) throws Exception { + if (length == 0) { + return ""; + } + + byte[] blob = new byte[length]; + assertEquals(length, reader.read(blob)); + return new String(blob); + } } diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/backup/DatabaseImporterTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/backup/DatabaseImporterTest.java index 05b13aeaa..916af61d3 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/io/backup/DatabaseImporterTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/backup/DatabaseImporterTest.java @@ -24,6 +24,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; +import java.io.IOException; import java.util.ArrayList; import junit.framework.TestCase; @@ -69,38 +70,27 @@ public class DatabaseImporterTest extends TestCase { public void testImportAllRows_readNullFields() throws Exception { testImportAllRows(true); } - + private void testImportAllRows(boolean readNullFields) throws Exception { // Create a fake data stream to be read ByteArrayOutputStream outputStream = new ByteArrayOutputStream(1024); DataOutputStream writer = new DataOutputStream(outputStream); - // Add the header - writer.writeInt(6); - writer.writeUTF("col1"); - writer.writeByte(ContentTypeIds.INT_TYPE_ID); - writer.writeUTF("col2"); - writer.writeByte(ContentTypeIds.BOOLEAN_TYPE_ID); - writer.writeUTF("col3"); - writer.writeByte(ContentTypeIds.STRING_TYPE_ID); - writer.writeUTF("col4"); - writer.writeByte(ContentTypeIds.FLOAT_TYPE_ID); - writer.writeUTF("col5"); - writer.writeByte(ContentTypeIds.DOUBLE_TYPE_ID); - writer.writeUTF("col6"); - writer.writeByte(ContentTypeIds.LONG_TYPE_ID); + writeFullHeader(writer); // Add the number of rows writer.writeInt(2); // Add a row with all fields present - writer.writeLong(0x3F); + writer.writeLong(0x7F); writer.writeInt(42); writer.writeBoolean(true); writer.writeUTF("lolcat"); writer.writeFloat(3.1415f); writer.writeDouble(2.72); writer.writeLong(123456789L); + writer.writeInt(4); + writer.writeBytes("blob"); // Add a row with some missing fields writer.writeLong(0x15); @@ -110,6 +100,7 @@ public class DatabaseImporterTest extends TestCase { if (readNullFields) writer.writeFloat(0.0f); writer.writeDouble(2.72); if (readNullFields) writer.writeLong(0L); + if (readNullFields) writer.writeInt(0); // empty blob writer.flush(); @@ -122,7 +113,7 @@ public class DatabaseImporterTest extends TestCase { // Verify the first row ContentValues value = insertedValues.get(0); - assertEquals(value.toString(), 6, value.size()); + assertEquals(value.toString(), 7, value.size()); assertValue(42, "col1", value); assertValue(true, "col2", value); @@ -130,6 +121,7 @@ public class DatabaseImporterTest extends TestCase { assertValue(3.1415f, "col4", value); assertValue(2.72, "col5", value); assertValue(123456789L, "col6", value); + assertBlobValue("blob", "col7", value); // Verify the second row value = insertedValues.get(1); @@ -139,26 +131,13 @@ public class DatabaseImporterTest extends TestCase { assertValue("lolcat", "col3", value); assertValue(2.72, "col5", value); } - + public void testImportAllRows_noRows() throws Exception { // Create a fake data stream to be read ByteArrayOutputStream outputStream = new ByteArrayOutputStream(1024); DataOutputStream writer = new DataOutputStream(outputStream); - // Add the header - writer.writeInt(6); - writer.writeUTF("col1"); - writer.writeByte(ContentTypeIds.INT_TYPE_ID); - writer.writeUTF("col2"); - writer.writeByte(ContentTypeIds.BOOLEAN_TYPE_ID); - writer.writeUTF("col3"); - writer.writeByte(ContentTypeIds.STRING_TYPE_ID); - writer.writeUTF("col4"); - writer.writeByte(ContentTypeIds.FLOAT_TYPE_ID); - writer.writeUTF("col5"); - writer.writeByte(ContentTypeIds.DOUBLE_TYPE_ID); - writer.writeUTF("col6"); - writer.writeByte(ContentTypeIds.LONG_TYPE_ID); + writeFullHeader(writer); // Add the number of rows writer.writeInt(0); @@ -186,20 +165,7 @@ public class DatabaseImporterTest extends TestCase { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(1024); DataOutputStream writer = new DataOutputStream(outputStream); - // Add the header - writer.writeInt(6); - writer.writeUTF("col1"); - writer.writeByte(ContentTypeIds.INT_TYPE_ID); - writer.writeUTF("col2"); - writer.writeByte(ContentTypeIds.BOOLEAN_TYPE_ID); - writer.writeUTF("col3"); - writer.writeByte(ContentTypeIds.STRING_TYPE_ID); - writer.writeUTF("col4"); - writer.writeByte(ContentTypeIds.FLOAT_TYPE_ID); - writer.writeUTF("col5"); - writer.writeByte(ContentTypeIds.DOUBLE_TYPE_ID); - writer.writeUTF("col6"); - writer.writeByte(ContentTypeIds.LONG_TYPE_ID); + writeFullHeader(writer); // Add the number of rows writer.writeInt(3); @@ -214,6 +180,7 @@ public class DatabaseImporterTest extends TestCase { writer.writeFloat(0.0f); writer.writeDouble(0.0); writer.writeLong(0L); + writer.writeInt(0); // empty blob } } @@ -225,6 +192,7 @@ public class DatabaseImporterTest extends TestCase { if (readNullFields) writer.writeFloat(0.0f); writer.writeDouble(2.72); if (readNullFields) writer.writeLong(0L); + if (readNullFields) writer.writeInt(0); // empty blob writer.flush(); @@ -292,10 +260,34 @@ public class DatabaseImporterTest extends TestCase { } } + private void writeFullHeader(DataOutputStream writer) throws IOException { + // Add the header + writer.writeInt(7); + writer.writeUTF("col1"); + writer.writeByte(ContentTypeIds.INT_TYPE_ID); + writer.writeUTF("col2"); + writer.writeByte(ContentTypeIds.BOOLEAN_TYPE_ID); + writer.writeUTF("col3"); + writer.writeByte(ContentTypeIds.STRING_TYPE_ID); + writer.writeUTF("col4"); + writer.writeByte(ContentTypeIds.FLOAT_TYPE_ID); + writer.writeUTF("col5"); + writer.writeByte(ContentTypeIds.DOUBLE_TYPE_ID); + writer.writeUTF("col6"); + writer.writeByte(ContentTypeIds.LONG_TYPE_ID); + writer.writeUTF("col7"); + writer.writeByte(ContentTypeIds.BLOB_TYPE_ID); + } + private void assertValue(T expectedValue, String name, ContentValues values) { @SuppressWarnings("unchecked") T value = (T) values.get(name); assertNotNull(value); assertEquals(expectedValue, value); } + + private void assertBlobValue(String expectedValue, String name, ContentValues values ){ + byte[] blob = values.getAsByteArray(name); + assertEquals(expectedValue, new String(blob)); + } }