forked from upstream-mirrors/OpenTracks
Adding backup facilities. TODO: some tests, calling to trigger new automatic backup on Froyo, manual backup to SD option
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* 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 com.google.android.apps.mytracks.content;
|
||||
|
||||
/**
|
||||
* Utilities for serializing primitive types.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class ContentTypeIds {
|
||||
public static final byte BOOLEAN_TYPE_ID = 0;
|
||||
public static final byte LONG_TYPE_ID = 1;
|
||||
public static final byte INT_TYPE_ID = 2;
|
||||
public static final byte FLOAT_TYPE_ID = 3;
|
||||
public static final byte DOUBLE_TYPE_ID = 4;
|
||||
public static final byte STRING_TYPE_ID = 5;
|
||||
|
||||
private ContentTypeIds() { /* Not instantiable */ }
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.google.android.apps.mytracks.content;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.location.Location;
|
||||
@@ -267,6 +268,24 @@ public interface MyTracksProviderUtils {
|
||||
*/
|
||||
void updateTrack(Track track);
|
||||
|
||||
/**
|
||||
* Creates a Track object from a given cursor.
|
||||
*
|
||||
* @param cursor a cursor pointing at a db or provider with tracks
|
||||
* @return a new Track object
|
||||
*/
|
||||
Track createTrack(Cursor cursor);
|
||||
|
||||
/**
|
||||
* Creates the ContentValues for a given Track object.
|
||||
*
|
||||
* Note: If the track has an id<0 the id column will not be filled.
|
||||
*
|
||||
* @param track a given track object
|
||||
* @return a filled in ContentValues object
|
||||
*/
|
||||
ContentValues createContentValues(Track track);
|
||||
|
||||
/**
|
||||
* Creates a location object from a given cursor.
|
||||
*
|
||||
|
||||
+4
-16
@@ -74,15 +74,8 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the ContentValues for a given Track object.
|
||||
*
|
||||
* Note: If the track has an id<0 the id column will not be filled.
|
||||
*
|
||||
* @param track a given track object
|
||||
* @return a filled in ContentValues object
|
||||
*/
|
||||
private static ContentValues createContentValues(Track track) {
|
||||
@Override
|
||||
public ContentValues createContentValues(Track track) {
|
||||
ContentValues values = new ContentValues();
|
||||
TripStatistics stats = track.getStatistics();
|
||||
|
||||
@@ -209,13 +202,8 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
return location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Track object from a given cursor.
|
||||
*
|
||||
* @param cursor a cursor pointing at a db or provider with tracks
|
||||
* @return a new Track object
|
||||
*/
|
||||
private static Track createTrack(Cursor cursor) {
|
||||
@Override
|
||||
public Track createTrack(Cursor cursor) {
|
||||
int idxId = cursor.getColumnIndexOrThrow(TracksColumns._ID);
|
||||
int idxName = cursor.getColumnIndexOrThrow(TracksColumns.NAME);
|
||||
int idxDescription =
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.google.android.apps.mytracks.content;
|
||||
|
||||
import static com.google.android.apps.mytracks.content.ContentTypeIds.*;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
@@ -42,4 +44,14 @@ public interface TrackPointsColumns extends BaseColumns {
|
||||
public static final String TIME = "time";
|
||||
public static final String ACCURACY = "accuracy";
|
||||
public static final String SPEED = "speed";
|
||||
|
||||
/** Columns that go into the backup. */
|
||||
public static final String[] BACKUP_COLUMNS =
|
||||
{ _ID, TRACKID, LATITUDE, LONGITUDE, ALTITUDE, BEARING, TIME,
|
||||
ACCURACY, SPEED };
|
||||
|
||||
/** Types of the columns that go into the backup. */
|
||||
public static final byte[] 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 };
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.google.android.apps.mytracks.content;
|
||||
|
||||
import static com.google.android.apps.mytracks.content.ContentTypeIds.*;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
@@ -59,4 +61,16 @@ public interface TracksColumns extends BaseColumns {
|
||||
public static final String MINLON = "minlon";
|
||||
public static final String MAXLON = "maxlon";
|
||||
public static final String MAPID = "mapid";
|
||||
|
||||
public static final String[] BACKUP_COLUMNS = {
|
||||
_ID, NAME, DESCRIPTION, CATEGORY, STARTID, STOPID, STARTTIME, STOPTIME,
|
||||
NUMPOINTS, TOTALDISTANCE, TOTALTIME, MOVINGTIME, AVGSPEED, AVGMOVINGSPEED,
|
||||
MAXSPEED, MINELEVATION, MAXELEVATION, ELEVATIONGAIN, MINGRADE, MAXGRADE,
|
||||
MINLAT, MAXLAT, MINLON, MAXLON };
|
||||
public static final byte[] BACKUP_COLUMN_TYPES = {
|
||||
LONG_TYPE_ID, STRING_TYPE_ID, STRING_TYPE_ID, LONG_TYPE_ID, LONG_TYPE_ID,
|
||||
LONG_TYPE_ID, LONG_TYPE_ID, INT_TYPE_ID, FLOAT_TYPE_ID, LONG_TYPE_ID,
|
||||
LONG_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID,
|
||||
FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID, INT_TYPE_ID,
|
||||
INT_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID };
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.google.android.apps.mytracks.content;
|
||||
|
||||
import static com.google.android.apps.mytracks.content.ContentTypeIds.*;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
@@ -66,4 +68,19 @@ public interface WaypointsColumns extends BaseColumns {
|
||||
public static final String ELEVATIONGAIN = "elevationgain";
|
||||
public static final String MINGRADE = "mingrade";
|
||||
public static final String MAXGRADE = "maxgrade";
|
||||
|
||||
public static final String[] BACKUP_COLUMNS = {
|
||||
_ID, TRACKID, NAME, DESCRIPTION, CATEGORY, ICON, TYPE, LENGTH, DURATION,
|
||||
STARTTIME, STARTID, STOPID, LATITUDE, LONGITUDE, ALTITUDE, BEARING, TIME,
|
||||
ACCURACY, SPEED, TOTALDISTANCE, TOTALTIME, MOVINGTIME, AVGSPEED,
|
||||
AVGMOVINGSPEED, MAXSPEED, MINELEVATION, MAXELEVATION, ELEVATIONGAIN,
|
||||
MINGRADE, MAXGRADE };
|
||||
public static final byte[] BACKUP_COLUMN_TYPES = {
|
||||
LONG_TYPE_ID, LONG_TYPE_ID, STRING_TYPE_ID, STRING_TYPE_ID,
|
||||
STRING_TYPE_ID, STRING_TYPE_ID, INT_TYPE_ID, FLOAT_TYPE_ID, LONG_TYPE_ID,
|
||||
LONG_TYPE_ID, 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, LONG_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID,
|
||||
FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID,
|
||||
FLOAT_TYPE_ID };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* 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 com.google.android.apps.mytracks.io.backup;
|
||||
|
||||
import android.app.backup.BackupDataInput;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Input stream which reads from backup data.
|
||||
* This is exactly the same as {@link android.app.backup.BackupDataInputStream},
|
||||
* but for some odd reason Android doesn't want us instantiating that one.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
class BackupDataInputStream extends InputStream {
|
||||
private final BackupDataInput data;
|
||||
private final byte[] oneByte = new byte[1];
|
||||
|
||||
public BackupDataInputStream(BackupDataInput data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
data.readEntityData(oneByte, 0, 1);
|
||||
return oneByte[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b) throws IOException {
|
||||
return data.readEntityData(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int offset, int length) throws IOException {
|
||||
return data.readEntityData(b, offset, length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* 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 com.google.android.apps.mytracks.io.backup;
|
||||
|
||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Manager responsible for reading and writing the backup state.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
class BackupStateManager {
|
||||
private static final byte BACKUP_VERSION = 1;
|
||||
|
||||
/**
|
||||
* The state of a single track.
|
||||
*/
|
||||
public static class TrackState {
|
||||
public final long trackId;
|
||||
public final String name;
|
||||
public final String description;
|
||||
public final String category;
|
||||
|
||||
TrackState(long trackId, String name, String description, String category) {
|
||||
this.trackId = trackId;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks and returns whether this state is representative of the given
|
||||
* track at its current state.
|
||||
*/
|
||||
public boolean represents(Track track) {
|
||||
return trackId == track.getId() &&
|
||||
name.equals(track.getName()) &&
|
||||
description.equals(track.getDescription()) &&
|
||||
category.equals(track.getCategory());
|
||||
}
|
||||
}
|
||||
|
||||
// File descriptors to read/write state from/to
|
||||
private final ParcelFileDescriptor oldState;
|
||||
private final ParcelFileDescriptor newState;
|
||||
|
||||
// Actual state data read from/to be written to the descriptors above
|
||||
private Map<Long, TrackState> oldTracks;
|
||||
private List<TrackState> newTrackState;
|
||||
|
||||
public BackupStateManager(ParcelFileDescriptor oldState, ParcelFileDescriptor newState) {
|
||||
this.oldState = oldState;
|
||||
this.newState = newState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the old track state.
|
||||
*
|
||||
* If any errors occur while reading, this will return a partial or empty map.
|
||||
*
|
||||
* @return a map of track ID to its state
|
||||
*/
|
||||
public Map<Long, TrackState> getOldTrackState() {
|
||||
if (oldTracks != null) {
|
||||
return oldTracks;
|
||||
}
|
||||
|
||||
oldTracks = new TreeMap<Long, TrackState>();
|
||||
if (!hasOldState()) {
|
||||
return oldTracks;
|
||||
}
|
||||
|
||||
try {
|
||||
InputStream stateInputStream = openStateInput();
|
||||
DataInputStream stateInput = new DataInputStream(stateInputStream);
|
||||
|
||||
// Read the version from the header
|
||||
if (stateInput.readByte() != BACKUP_VERSION) {
|
||||
throw new IllegalStateException("Bad backup version");
|
||||
}
|
||||
|
||||
int previousNumberOfTracks = stateInput.readInt();
|
||||
for (int i = 0; i < previousNumberOfTracks; i++) {
|
||||
TrackState oldTrack = readTrackState(stateInput);
|
||||
oldTracks.put(oldTrack.trackId, oldTrack);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// If we fail to read, return whatever we got - this only means
|
||||
// we'll be backing up more than we should.
|
||||
Log.e(MyTracksConstants.TAG, "Unable to read old state", e);
|
||||
}
|
||||
return oldTracks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we have a known old state.
|
||||
*/
|
||||
protected boolean hasOldState() {
|
||||
return oldState != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the old state and returns a stream to read it.
|
||||
*/
|
||||
protected InputStream openStateInput() {
|
||||
return new FileInputStream(oldState.getFileDescriptor());
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the new state and returns a stream to write to it.
|
||||
*/
|
||||
protected OutputStream openStateOutput() {
|
||||
return new FileOutputStream(newState.getFileDescriptor());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the state of a single track.
|
||||
*
|
||||
* @param stateInput the stream to read from
|
||||
* @return the state read
|
||||
* @throws IOException if reading fails
|
||||
*/
|
||||
private TrackState readTrackState(DataInputStream stateInput) throws IOException {
|
||||
long trackId = stateInput.readLong();
|
||||
String name = stateInput.readUTF();
|
||||
String desc = stateInput.readUTF();
|
||||
String category = stateInput.readUTF();
|
||||
return new TrackState(trackId, name, desc, category);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a track to the current/new state.
|
||||
* Please notice that the state is not actually written until
|
||||
* {@link #flushNewState} is called.
|
||||
*/
|
||||
public void addTrackState(Track track) {
|
||||
ensureTrackState();
|
||||
|
||||
newTrackState.add(new TrackState(track.getId(), track.getName(),
|
||||
track.getDescription(), track.getCategory()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually writes out the new state to its output.
|
||||
*
|
||||
* @throws IOException if there are errors while writing
|
||||
*/
|
||||
public void flushNewState() throws IOException {
|
||||
ensureTrackState();
|
||||
|
||||
OutputStream stateOutputStream = openStateOutput();
|
||||
DataOutputStream stateOutput = new DataOutputStream(stateOutputStream);
|
||||
|
||||
stateOutput.writeByte(BACKUP_VERSION);
|
||||
|
||||
stateOutput.writeInt(newTrackState.size());
|
||||
for (TrackState track : newTrackState) {
|
||||
writeTrackState(stateOutput, track);
|
||||
}
|
||||
|
||||
stateOutput.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the track state buffer exists.
|
||||
*/
|
||||
private void ensureTrackState() {
|
||||
if (newTrackState == null) {
|
||||
newTrackState = new ArrayList<TrackState>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the state of a single track.
|
||||
*
|
||||
* @param stateOutput the stream to write to
|
||||
* @param track the track state to write
|
||||
* @throws IOException if there are errors while writing
|
||||
*/
|
||||
private void writeTrackState(DataOutputStream stateOutput, TrackState track) throws IOException {
|
||||
stateOutput.writeLong(track.trackId);
|
||||
stateOutput.writeUTF(track.name);
|
||||
stateOutput.writeUTF(track.description);
|
||||
stateOutput.writeUTF(track.category);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* 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 com.google.android.apps.mytracks.io.backup;
|
||||
|
||||
import com.google.android.apps.mytracks.content.ContentTypeIds;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.database.MergeCursor;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Database dumper which is able to write only part of the database
|
||||
* according to some query.
|
||||
*
|
||||
* This dumper is symmetrical to {@link DatabaseImporter}.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
class DatabaseDumper {
|
||||
|
||||
/** The names of the columns being dumped. */
|
||||
private final String[] columnNames;
|
||||
/** The types of the columns being dumped. */
|
||||
private final byte[] columnTypes;
|
||||
/** Whether to output null fields. */
|
||||
private final boolean outputNullFields;
|
||||
|
||||
// Temporary state
|
||||
private int[] columnIndices;
|
||||
private boolean[] hasFields;
|
||||
|
||||
public DatabaseDumper(String[] columnNames, byte[] columnTypes,
|
||||
boolean outputNullFields) {
|
||||
if (columnNames.length != columnTypes.length) {
|
||||
throw new IllegalArgumentException("Names don't match types");
|
||||
}
|
||||
|
||||
this.columnNames = columnNames;
|
||||
this.columnTypes = columnTypes;
|
||||
this.outputNullFields = outputNullFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the header plus all rows that can be read from the given cursor.
|
||||
* This assumes the cursor will have the same column and column indices on
|
||||
* every row (and thus may not work with a {@link MergeCursor}).
|
||||
*/
|
||||
public void writeAllRows(Cursor cursor, DataOutputStream writer) throws IOException {
|
||||
writeHeaders(cursor, cursor.getCount(), writer);
|
||||
|
||||
if (!cursor.moveToFirst()) {
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
writeOneRow(cursor, writer);
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes just the headers for the data that will come from the given cursor.
|
||||
* The headers include column information and the number of rows that will be
|
||||
* written.
|
||||
*
|
||||
* @param cursor the cursor to get columns from
|
||||
* @param numRows the number of rows that will be later written
|
||||
* @throws IOException if there are errors while writing
|
||||
*/
|
||||
public void writeHeaders(Cursor cursor, int numRows, DataOutputStream writer) throws IOException {
|
||||
initializeCachedValues(cursor);
|
||||
writeQueryMetadata(cursor, numRows, writer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the current row from the cursor. The cursor is not advanced.
|
||||
* This must be called after {@link #writeHeaders}.
|
||||
*
|
||||
* @param cursor the cursor to write data from
|
||||
* @throws IOException if there are any errors while writing
|
||||
*/
|
||||
public void writeOneRow(Cursor cursor, DataOutputStream writer) throws IOException {
|
||||
if (columnIndices == null) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot write rows before writing the header");
|
||||
}
|
||||
|
||||
if (columnIndices.length > Long.SIZE) {
|
||||
throw new IllegalArgumentException("Too many fields");
|
||||
}
|
||||
|
||||
// Build a bitmap of which fields are present
|
||||
long fields = 0;
|
||||
for (int i = 0; i < columnIndices.length; i++) {
|
||||
hasFields[i] = !cursor.isNull(columnIndices[i]);
|
||||
fields |= (hasFields[i] ? 1 : 0) << i;
|
||||
}
|
||||
writer.writeLong(fields);
|
||||
|
||||
// Actually write the present fields
|
||||
for (int i = 0; i < columnIndices.length; i++) {
|
||||
if (hasFields[i]) {
|
||||
writeCell(columnIndices[i], columnTypes[i], cursor, writer);
|
||||
} else if (outputNullFields) {
|
||||
writeDummyCell(columnTypes[i], writer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the column indices and other temporary state for reading from
|
||||
* the given cursor.
|
||||
*/
|
||||
private void initializeCachedValues(Cursor cursor) {
|
||||
// These indices are constant for every row (unless we're fed a MergeCursor)
|
||||
if (cursor instanceof MergeCursor) {
|
||||
throw new IllegalArgumentException("Cannot use a MergeCursor");
|
||||
}
|
||||
|
||||
columnIndices = new int[columnNames.length];
|
||||
for (int i = 0; i < columnNames.length; i++) {
|
||||
String columnName = columnNames[i];
|
||||
columnIndices[i] = cursor.getColumnIndexOrThrow(columnName);
|
||||
}
|
||||
|
||||
hasFields = new boolean[columnIndices.length];
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes metadata about the query to be dumped.
|
||||
*
|
||||
* @param cursor the cursor that the data will come from
|
||||
* @param numRows the number of rows that will be dumped
|
||||
* @throws IOException if there are any errors while writing
|
||||
*/
|
||||
private void writeQueryMetadata(Cursor cursor, int numRows, DataOutputStream writer) throws IOException {
|
||||
// Write column data
|
||||
writer.writeInt(columnNames.length);
|
||||
for (int i = 0; i < columnNames.length; i++) {
|
||||
String columnName = columnNames[i];
|
||||
byte columnType = columnTypes[i];
|
||||
writer.writeUTF(columnName);
|
||||
writer.writeByte(columnType);
|
||||
}
|
||||
|
||||
// Write the number of rows
|
||||
writer.writeInt(numRows);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 cursor the cursor to read from
|
||||
* @throws IOException if there are any errors while writing
|
||||
*/
|
||||
private void writeCell(int columnIdx, byte columnTypeId, Cursor cursor, DataOutputStream writer) throws IOException {
|
||||
switch (columnTypeId) {
|
||||
case ContentTypeIds.LONG_TYPE_ID:
|
||||
writer.writeLong(cursor.getLong(columnIdx));
|
||||
return;
|
||||
case ContentTypeIds.DOUBLE_TYPE_ID:
|
||||
writer.writeDouble(cursor.getDouble(columnIdx));
|
||||
return;
|
||||
case ContentTypeIds.FLOAT_TYPE_ID:
|
||||
writer.writeFloat(cursor.getFloat(columnIdx));
|
||||
return;
|
||||
case ContentTypeIds.BOOLEAN_TYPE_ID:
|
||||
writer.writeBoolean(cursor.getInt(columnIdx) != 0);
|
||||
return;
|
||||
case ContentTypeIds.INT_TYPE_ID:
|
||||
writer.writeInt(cursor.getInt(columnIdx));
|
||||
return;
|
||||
case ContentTypeIds.STRING_TYPE_ID:
|
||||
writer.writeUTF(cursor.getString(columnIdx));
|
||||
return;
|
||||
default:
|
||||
throw new IllegalArgumentException("Type " + columnTypeId + " not supported");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a dummy cell value to the output.
|
||||
*
|
||||
* @param columnType the type of the value to write
|
||||
* @throws IOException if there are any errors while writing
|
||||
*/
|
||||
private void writeDummyCell(byte columnTypeId, DataOutputStream writer) throws IOException {
|
||||
switch (columnTypeId) {
|
||||
case ContentTypeIds.LONG_TYPE_ID:
|
||||
writer.writeLong(0L);
|
||||
return;
|
||||
case ContentTypeIds.DOUBLE_TYPE_ID:
|
||||
writer.writeDouble(0.0);
|
||||
return;
|
||||
case ContentTypeIds.FLOAT_TYPE_ID:
|
||||
writer.writeFloat(0.0f);
|
||||
return;
|
||||
case ContentTypeIds.BOOLEAN_TYPE_ID:
|
||||
writer.writeBoolean(false);
|
||||
return;
|
||||
case ContentTypeIds.INT_TYPE_ID:
|
||||
writer.writeInt(0);
|
||||
return;
|
||||
case ContentTypeIds.STRING_TYPE_ID:
|
||||
writer.writeUTF("");
|
||||
return;
|
||||
default:
|
||||
throw new IllegalArgumentException("Type " + columnTypeId + " not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* 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 com.google.android.apps.mytracks.io.backup;
|
||||
|
||||
import com.google.android.apps.mytracks.content.ContentTypeIds;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Database importer which reads values written by {@link DatabaseDumper}.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class DatabaseImporter {
|
||||
|
||||
/** Maximum number of entries in a bulk insertion */
|
||||
private static final int DEFAULT_BULK_SIZE = 1024;
|
||||
|
||||
private final Uri destinationUri;
|
||||
private final ContentResolver resolver;
|
||||
private final boolean readNullFields;
|
||||
private final int bulkSize;
|
||||
|
||||
// Metadata read from the reader
|
||||
private String[] columnNames;
|
||||
private byte[] columnTypes;
|
||||
|
||||
public DatabaseImporter(Uri destinationUri, ContentResolver resolver,
|
||||
boolean readNullFields) {
|
||||
this(destinationUri, resolver, readNullFields, DEFAULT_BULK_SIZE);
|
||||
}
|
||||
|
||||
protected DatabaseImporter(Uri destinationUri, ContentResolver resolver,
|
||||
boolean readNullFields, int bulkSize) {
|
||||
this.destinationUri = destinationUri;
|
||||
this.resolver = resolver;
|
||||
this.readNullFields = readNullFields;
|
||||
this.bulkSize = bulkSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the header which includes metadata about the table being imported.
|
||||
*
|
||||
* @throws IOException if there are any problems while reading
|
||||
*/
|
||||
private void readHeaders(DataInputStream reader) throws IOException {
|
||||
int numColumns = reader.readInt();
|
||||
columnNames = new String[numColumns];
|
||||
columnTypes = new byte[numColumns];
|
||||
for (int i = 0; i < numColumns; i++) {
|
||||
columnNames[i] = reader.readUTF();
|
||||
columnTypes[i] = reader.readByte();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports all rows from the reader into the database.
|
||||
* Insertion is done in bulks for efficiency.
|
||||
*
|
||||
* @throws IOException if there are any errors while reading
|
||||
*/
|
||||
public void importAllRows(DataInputStream reader) throws IOException {
|
||||
readHeaders(reader);
|
||||
|
||||
ContentValues[] valueBulk = new ContentValues[bulkSize];
|
||||
int numValues = 0;
|
||||
|
||||
int numRows = reader.readInt();
|
||||
int numColumns = columnNames.length;
|
||||
|
||||
// For each row
|
||||
for (int r = 0; r < numRows; r++) {
|
||||
if (valueBulk[numValues] == null) {
|
||||
valueBulk[numValues] = new ContentValues(numColumns);
|
||||
} else {
|
||||
// Reuse values objects
|
||||
valueBulk[numValues].clear();
|
||||
}
|
||||
|
||||
// Read the fields bitmap
|
||||
long fields = reader.readLong();
|
||||
for (int c = 0; c < numColumns; c++) {
|
||||
if ((fields & 1) == 1) {
|
||||
// Field is present, read into values
|
||||
readOneCell(columnNames[c], columnTypes[c], valueBulk[numValues], reader);
|
||||
} else if (readNullFields) {
|
||||
// Field not present but still written, read and discard
|
||||
readOneCell(columnNames[c], columnTypes[c], null, reader);
|
||||
}
|
||||
|
||||
fields >>= 1;
|
||||
}
|
||||
|
||||
numValues++;
|
||||
|
||||
// If we have enough values, flush them as a bulk insertion
|
||||
if (numValues >= bulkSize) {
|
||||
doBulkInsert(valueBulk);
|
||||
numValues = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Do a final bulk insert with the leftovers
|
||||
if (numValues > 0) {
|
||||
ContentValues[] leftovers = new ContentValues[numValues];
|
||||
System.arraycopy(valueBulk, 0, leftovers, 0, numValues);
|
||||
doBulkInsert(leftovers);
|
||||
}
|
||||
}
|
||||
|
||||
protected void doBulkInsert(ContentValues[] values) {
|
||||
resolver.bulkInsert(destinationUri, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a single cell from the reader.
|
||||
*
|
||||
* @param name the name of the column to be read
|
||||
* @param typeId the type ID of the column to be read
|
||||
* @param values the {@link ContentValues} object to put the read cell value
|
||||
* in - if null, the value is just discarded
|
||||
* @throws IOException if there are any problems while reading
|
||||
*/
|
||||
private void readOneCell(String name, byte typeId, ContentValues values, DataInputStream reader) throws IOException {
|
||||
switch (typeId) {
|
||||
case ContentTypeIds.BOOLEAN_TYPE_ID: {
|
||||
boolean value = reader.readBoolean();
|
||||
if (values != null) { values.put(name, value); }
|
||||
return;
|
||||
}
|
||||
case ContentTypeIds.LONG_TYPE_ID: {
|
||||
long value = reader.readLong();
|
||||
if (values != null) { values.put(name, value); }
|
||||
return;
|
||||
}
|
||||
case ContentTypeIds.DOUBLE_TYPE_ID: {
|
||||
double value = reader.readDouble();
|
||||
if (values != null) { values.put(name, value); }
|
||||
return;
|
||||
}
|
||||
case ContentTypeIds.FLOAT_TYPE_ID: {
|
||||
Float value = reader.readFloat();
|
||||
if (values != null) { values.put(name, value); }
|
||||
return;
|
||||
}
|
||||
case ContentTypeIds.INT_TYPE_ID: {
|
||||
int value = reader.readInt();
|
||||
if (values != null) { values.put(name, value); }
|
||||
return;
|
||||
}
|
||||
case ContentTypeIds.STRING_TYPE_ID: {
|
||||
String value = reader.readUTF();
|
||||
if (values != null) { values.put(name, value); }
|
||||
return;
|
||||
}
|
||||
default:
|
||||
throw new IOException("Read unknown type " + typeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* 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 com.google.android.apps.mytracks.io.backup;
|
||||
|
||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||
import com.google.android.apps.mytracks.MyTracksSettings;
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.TrackPointsColumns;
|
||||
import com.google.android.apps.mytracks.content.TracksColumns;
|
||||
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
||||
import com.google.android.apps.mytracks.io.backup.BackupStateManager.TrackState;
|
||||
|
||||
import android.app.backup.BackupAgent;
|
||||
import android.app.backup.BackupDataInput;
|
||||
import android.app.backup.BackupDataOutput;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Backup agent used to backup and restore all preferences and tracks.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class MyTracksBackupAgent extends BackupAgent {
|
||||
private static final String PREFERENCES_ENTITY = "prefs";
|
||||
|
||||
private static final String TRACK_ENTITY_PREFIX = "track_";
|
||||
|
||||
/**
|
||||
* Initial size of the buffer used to back up a track.
|
||||
*/
|
||||
private static final int INITIAL_BUFFER_SIZE = 4096;
|
||||
|
||||
/**
|
||||
* Size of the buffer after which it's flushed into the backup.
|
||||
*/
|
||||
private static final int MAX_BUFFER_SIZE = 32768;
|
||||
|
||||
/**
|
||||
* Utilities for accessing the content provider.
|
||||
*/
|
||||
private MyTracksProviderUtils providerUtils;
|
||||
|
||||
// Database dumpers
|
||||
protected DatabaseDumper trackDumper;
|
||||
protected DatabaseDumper waypointDumper;
|
||||
protected DatabaseDumper pointDumper;
|
||||
|
||||
// Database importers
|
||||
protected DatabaseImporter trackImporter;
|
||||
protected DatabaseImporter waypointImporter;
|
||||
protected DatabaseImporter pointImporter;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
providerUtils = MyTracksProviderUtils.Factory.get(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
|
||||
ParcelFileDescriptor newState) throws IOException {
|
||||
// TODO: Trigger backup on: track stop, preference change, track edit
|
||||
// TODO: How to handle backup while recording? (track may still change)
|
||||
BackupStateManager backupStateManager = createStateManager(oldState, newState);
|
||||
BackupStateManager stateManager = backupStateManager;
|
||||
|
||||
backupPreferences(data);
|
||||
backupTracks(data, stateManager);
|
||||
|
||||
stateManager.flushNewState();
|
||||
}
|
||||
|
||||
protected BackupStateManager createStateManager(ParcelFileDescriptor oldState,
|
||||
ParcelFileDescriptor newState) {
|
||||
return new BackupStateManager(oldState, newState);
|
||||
}
|
||||
|
||||
private void backupPreferences(BackupDataOutput data) throws IOException {
|
||||
SharedPreferences preferences = this.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
||||
PreferenceBackupHelper preferenceDumper = createPreferenceBackupHelper();
|
||||
byte[] dumpedContents = preferenceDumper.exportPreferences(preferences);
|
||||
data.writeEntityHeader(PREFERENCES_ENTITY, dumpedContents.length);
|
||||
data.writeEntityData(dumpedContents, dumpedContents.length);
|
||||
}
|
||||
|
||||
protected PreferenceBackupHelper createPreferenceBackupHelper() {
|
||||
return new PreferenceBackupHelper();
|
||||
}
|
||||
|
||||
/**
|
||||
* Backs up all the tracks.
|
||||
*
|
||||
* @param data the backup data to read from
|
||||
* @param stateManager the state manager to read from/write to
|
||||
* @throws IOException if there are any errors while reading or writing
|
||||
*/
|
||||
private void backupTracks(BackupDataOutput data, BackupStateManager stateManager) throws IOException {
|
||||
Cursor tracksCursor = providerUtils.getTracksCursor(null);
|
||||
if (!tracksCursor.moveToFirst()) {
|
||||
Log.w(MyTracksConstants.TAG, "Nothing to back up");
|
||||
return;
|
||||
}
|
||||
|
||||
ensureDumpers();
|
||||
|
||||
// For each existing track
|
||||
Map<Long, TrackState> oldTrackState = stateManager.getOldTrackState();
|
||||
Set<Long> currentTrackIds = new HashSet<Long>(tracksCursor.getCount());
|
||||
do {
|
||||
// Check if not already backed up
|
||||
Track track = providerUtils.createTrack(tracksCursor);
|
||||
TrackState oldTrack = oldTrackState.get(track.getId());
|
||||
if (oldTrack != null && oldTrack.represents(track)) {
|
||||
// Track is already backed up and hasn't been changed
|
||||
continue;
|
||||
}
|
||||
|
||||
// Back it up
|
||||
stateManager.addTrackState(track);
|
||||
writeTrack(data, track, tracksCursor);
|
||||
} while (tracksCursor.moveToNext());
|
||||
tracksCursor.close();
|
||||
|
||||
// For each previously-backed-up track, minus those that currently exist
|
||||
// (=only those that no longer exist)
|
||||
Set<Long> deletedTrackIds = new HashSet<Long>(oldTrackState.keySet());
|
||||
deletedTrackIds.removeAll(currentTrackIds);
|
||||
for (Long trackId : deletedTrackIds) {
|
||||
// Delete it from the backup
|
||||
data.writeEntityHeader(TRACK_ENTITY_PREFIX + trackId, -1);
|
||||
}
|
||||
}
|
||||
|
||||
protected void ensureDumpers() {
|
||||
if (trackDumper == null) {
|
||||
trackDumper =
|
||||
new DatabaseDumper(
|
||||
TracksColumns.BACKUP_COLUMNS,
|
||||
TracksColumns.BACKUP_COLUMN_TYPES,
|
||||
false);
|
||||
waypointDumper =
|
||||
new DatabaseDumper(
|
||||
WaypointsColumns.BACKUP_COLUMNS,
|
||||
WaypointsColumns.BACKUP_COLUMN_TYPES,
|
||||
false);
|
||||
pointDumper =
|
||||
new DatabaseDumper(
|
||||
TrackPointsColumns.BACKUP_COLUMNS,
|
||||
TrackPointsColumns.BACKUP_COLUMN_TYPES,
|
||||
true); // Keep the size of each entry constant
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a single track to the backup.
|
||||
*
|
||||
* @param data the backup data to write to
|
||||
* @param track the track to write
|
||||
* @param tracksCursor the tracks database cursor pointing to the given track
|
||||
* @throws IOException if there are any problems while writing
|
||||
*/
|
||||
private void writeTrack(BackupDataOutput data, Track track, Cursor tracksCursor) throws IOException {
|
||||
// Write the header
|
||||
ByteArrayOutputStream bufStream = new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
|
||||
DataOutputStream outWriter = new DataOutputStream(bufStream);
|
||||
|
||||
// Write track metadata
|
||||
trackDumper.writeHeaders(tracksCursor, 1, outWriter);
|
||||
trackDumper.writeOneRow(tracksCursor, outWriter);
|
||||
|
||||
// Write waypoints
|
||||
Cursor waypointsCursor = providerUtils.getWaypointsCursor(track.getId(), -1, -1);
|
||||
waypointDumper.writeAllRows(waypointsCursor, outWriter);
|
||||
waypointsCursor.close();
|
||||
|
||||
// Read points from the database
|
||||
Cursor pointsCursor = providerUtils.getLocationsCursor(track.getId(), 0, -1, false);
|
||||
|
||||
// Write the header for the points
|
||||
int numPoints = pointsCursor.getCount();
|
||||
pointDumper.writeHeaders(pointsCursor, numPoints, outWriter);
|
||||
|
||||
// Write the first point to the output and calculate its size
|
||||
// Since all points have the same size in the output, we can finally predict
|
||||
// the total size of the track, and write the backup header.
|
||||
int oldSize = bufStream.size();
|
||||
pointDumper.writeOneRow(pointsCursor, outWriter);
|
||||
int newSize = bufStream.size();
|
||||
int trackPointsSize = numPoints * (newSize - oldSize);
|
||||
int totalSize = oldSize + trackPointsSize;
|
||||
|
||||
// Write out the backup header
|
||||
data.writeEntityHeader(TRACK_ENTITY_PREFIX + track.getId(), totalSize);
|
||||
|
||||
// Write the rest of the points, in chunks
|
||||
while (pointsCursor.moveToNext()) {
|
||||
if (bufStream.size() >= MAX_BUFFER_SIZE) {
|
||||
outWriter.flush();
|
||||
data.writeEntityData(bufStream.toByteArray(), bufStream.size());
|
||||
bufStream.reset();
|
||||
}
|
||||
|
||||
pointDumper.writeOneRow(pointsCursor, outWriter);
|
||||
}
|
||||
pointsCursor.close();
|
||||
|
||||
// Do the last flushing of the buffer
|
||||
outWriter.flush();
|
||||
data.writeEntityData(bufStream.toByteArray(), bufStream.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRestore(BackupDataInput data, int appVersionCode,
|
||||
ParcelFileDescriptor newState) throws IOException {
|
||||
BackupStateManager stateManager = new BackupStateManager(null, newState);
|
||||
providerUtils.deleteAllTracks();
|
||||
|
||||
ensureImporters();
|
||||
|
||||
while (data.readNextHeader()) {
|
||||
String key = data.getKey();
|
||||
if (key.startsWith(TRACK_ENTITY_PREFIX)) {
|
||||
Track restoredTrack = restoreTrack(data);
|
||||
stateManager.addTrackState(restoredTrack);
|
||||
} else if (key.startsWith(PREFERENCES_ENTITY)) {
|
||||
restorePreferences(data);
|
||||
} else {
|
||||
Log.e(MyTracksConstants.TAG, "Found unknown backup entity: " + key);
|
||||
data.skipEntityData();
|
||||
}
|
||||
}
|
||||
|
||||
stateManager.flushNewState();
|
||||
}
|
||||
|
||||
protected void ensureImporters() {
|
||||
if (trackImporter == null) {
|
||||
ContentResolver resolver = this.getContentResolver();
|
||||
trackImporter =
|
||||
new DatabaseImporter(TracksColumns.CONTENT_URI, resolver, false);
|
||||
waypointImporter =
|
||||
new DatabaseImporter(WaypointsColumns.CONTENT_URI, resolver, false);
|
||||
pointImporter =
|
||||
new DatabaseImporter(TrackPointsColumns.CONTENT_URI, resolver, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores all preferences from the backup.
|
||||
*
|
||||
* @param data the backup data to read from
|
||||
* @throws IOException if there are any errors while reading
|
||||
*/
|
||||
private void restorePreferences(BackupDataInput data) throws IOException {
|
||||
int dataSize = data.getDataSize();
|
||||
byte[] dataBuffer = new byte[dataSize];
|
||||
int read = data.readEntityData(dataBuffer, 0, dataSize);
|
||||
if (read != dataSize) {
|
||||
throw new IOException("Failed to read all the preferences data");
|
||||
}
|
||||
|
||||
SharedPreferences preferences = this.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
||||
PreferenceBackupHelper importer = createPreferenceBackupHelper();
|
||||
importer.importPreferences(dataBuffer, preferences);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores a single track from the backup.
|
||||
*
|
||||
* @param data the data to restore from
|
||||
* @return the restored track
|
||||
* @throws IOException
|
||||
*/
|
||||
private Track restoreTrack(BackupDataInput data) throws IOException {
|
||||
BackupDataInputStream inputStream = new BackupDataInputStream(data);
|
||||
DataInputStream reader = new DataInputStream(inputStream);
|
||||
|
||||
trackImporter.importAllRows(reader);
|
||||
waypointImporter.importAllRows(reader);
|
||||
pointImporter.importAllRows(reader);
|
||||
|
||||
// Get the expected track ID from the entity key
|
||||
String key = data.getKey();
|
||||
int splitPos = key.lastIndexOf('_');
|
||||
String trackIdStr = key.substring(splitPos + 1);
|
||||
long trackId = Long.parseLong(trackIdStr);
|
||||
|
||||
return providerUtils.getTrack(trackId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* 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 com.google.android.apps.mytracks.io.backup;
|
||||
|
||||
import com.google.android.apps.mytracks.content.ContentTypeIds;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Helper for backing up and restoring shared preferences.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
class PreferenceBackupHelper {
|
||||
|
||||
private static final int BUFFER_SIZE = 2048;
|
||||
|
||||
/**
|
||||
* Exports all shared preferences from the given object as a byte array.
|
||||
*
|
||||
* @param preferences the preferences to export
|
||||
* @return the corresponding byte array
|
||||
* @throws IOException if there are any errors while writing to the byte array
|
||||
*/
|
||||
public byte[] exportPreferences(SharedPreferences preferences) throws IOException {
|
||||
ByteArrayOutputStream bufStream = new ByteArrayOutputStream(BUFFER_SIZE);
|
||||
DataOutputStream outWriter = new DataOutputStream(bufStream);
|
||||
Map<String, ?> values = preferences.getAll();
|
||||
|
||||
outWriter.writeInt(values.size());
|
||||
for (Map.Entry<String, ?> entry : values.entrySet()) {
|
||||
writePreference(entry.getKey(), entry.getValue(), outWriter);
|
||||
}
|
||||
outWriter.flush();
|
||||
|
||||
return bufStream.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports all preferences from the given byte array.
|
||||
*
|
||||
* @param data the byte array to read preferences from
|
||||
* @param preferences the shared preferences to edit
|
||||
* @return whether the preference change was successful
|
||||
* @throws IOException if there are any errors while reading
|
||||
*/
|
||||
public boolean importPreferences(byte[] data, SharedPreferences preferences) throws IOException {
|
||||
Editor editor = preferences.edit();
|
||||
editor.clear();
|
||||
|
||||
ByteArrayInputStream bufStream = new ByteArrayInputStream(data);
|
||||
DataInputStream reader = new DataInputStream(bufStream);
|
||||
|
||||
int numPreferneces = reader.readInt();
|
||||
for (int i = 0; i < numPreferneces; i++) {
|
||||
String name = reader.readUTF();
|
||||
byte typeId = reader.readByte();
|
||||
readAndSetPreference(name, typeId, reader, editor);
|
||||
}
|
||||
|
||||
return editor.commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a single preference and sets it into the given editor.
|
||||
*
|
||||
* @param name the name of the preference to read
|
||||
* @param typeId the type ID of the preference to read
|
||||
* @param reader the reader to read from
|
||||
* @param editor the editor to set the preference in
|
||||
* @throws IOException if there are errors while reading
|
||||
*/
|
||||
private void readAndSetPreference(String name, byte typeId,
|
||||
DataInputStream reader, Editor editor) throws IOException {
|
||||
switch (typeId) {
|
||||
case ContentTypeIds.BOOLEAN_TYPE_ID:
|
||||
editor.putBoolean(name, reader.readBoolean());
|
||||
return;
|
||||
case ContentTypeIds.LONG_TYPE_ID:
|
||||
editor.putLong(name, reader.readLong());
|
||||
return;
|
||||
case ContentTypeIds.FLOAT_TYPE_ID:
|
||||
editor.putFloat(name, reader.readFloat());
|
||||
return;
|
||||
case ContentTypeIds.INT_TYPE_ID:
|
||||
editor.putInt(name, reader.readInt());
|
||||
return;
|
||||
case ContentTypeIds.STRING_TYPE_ID:
|
||||
editor.putString(name, reader.readUTF());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a single preference.
|
||||
*
|
||||
* @param name the name of the preference to write
|
||||
* @param value the correctly-typed value of the preference
|
||||
* @param writer the writer to write to
|
||||
* @throws IOException if there are errors while writing
|
||||
*/
|
||||
private void writePreference(String name, Object value, DataOutputStream writer) throws IOException {
|
||||
writer.writeUTF(name);
|
||||
|
||||
if (value instanceof Boolean) {
|
||||
writer.writeByte(ContentTypeIds.BOOLEAN_TYPE_ID);
|
||||
writer.writeBoolean((Boolean) value);
|
||||
} else if (value instanceof Integer) {
|
||||
writer.writeByte(ContentTypeIds.INT_TYPE_ID);
|
||||
writer.writeInt((Integer) value);
|
||||
} else if (value instanceof Long) {
|
||||
writer.writeByte(ContentTypeIds.LONG_TYPE_ID);
|
||||
writer.writeLong((Long) value);
|
||||
} else if (value instanceof Float) {
|
||||
writer.writeByte(ContentTypeIds.FLOAT_TYPE_ID);
|
||||
writer.writeFloat((Float) value);
|
||||
} else if (value instanceof String) {
|
||||
writer.writeByte(ContentTypeIds.STRING_TYPE_ID);
|
||||
writer.writeUTF((String) value);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Type " + value.getClass().getName() + " not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user