Getting rid of full cloud backup for now - the backup service has a small size limit (1MB) which wouldn't be enough for us

This commit is contained in:
Rodrigo Damazio
2010-08-24 17:38:35 -03:00
parent 60ea4c5f77
commit 798637d465
8 changed files with 43 additions and 458 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
android:versionName="1.0.15">
<application android:icon="@drawable/arrow_icon"
android:label="@string/app_name"
android:backupAgent="com.google.android.apps.mytracks.io.backup.MyTracksBackupAgent">
android:backupAgent="com.google.android.apps.mytracks.io.backup.MyTracksBackupAgentHelper">
<uses-library android:name="com.google.android.maps" />
<meta-data android:name="com.google.android.backup.api_key"
-1
View File
@@ -41,5 +41,4 @@ limitations under the License.
<string name="split_frequency_key">splitFrequency</string>
<string name="backup_to_sd_key">backupToSd</string>
<string name="restore_from_sd_key">restoreFromSd</string>
<string name="backup_to_cloud_key">backupToCloud</string>
</resources>
-4
View File
@@ -225,10 +225,6 @@ limitations under the License.
<string name="settings_backup_to_sd_summary">Write a full backup of all your tracks to the SD card</string>
<string name="settings_restore_from_sd">Restore now</string>
<string name="settings_restore_from_sd_summary">Restore a backup from the SD card</string>
<string name="settings_cloud_backup">Cloud Backup</string>
<string name="settings_backup_to_cloud">Enable cloud backup</string>
<string name="settings_backup_to_cloud_summary_on">Your data will be automatically copied to servers</string>
<string name="settings_backup_to_cloud_summary_off">Your data will NOT be copied to servers</string>
<!-- Backups -->
<string name="restore_overwrites_warning">Restoring a backup from the SD card will OVERWRITE all your current tracks. Are you sure?</string>
-8
View File
@@ -124,13 +124,5 @@ limitations under the License.
android:summary="@string/settings_restore_from_sd_summary"
android:persistent="false" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/settings_cloud_backup">
<CheckBoxPreference
android:key="@string/backup_to_cloud_key"
android:title="@string/settings_backup_to_cloud"
android:summaryOn="@string/settings_backup_to_cloud_summary_on"
android:summaryOff="@string/settings_backup_to_cloud_summary_off"
android:defaultValue="true" />
</PreferenceCategory>
</PreferenceScreen>
</PreferenceScreen>
@@ -102,16 +102,9 @@ public class MyTracksSettings extends PreferenceActivity {
R.string.settings_announcement_not_available_summary);
}
// Disable cloud backup options if not supported
if (MyTracksConstants.ANDROID_API_LEVEL < 8) {
CheckBoxPreference cloudBackupPreference =
(CheckBoxPreference) findPreference(getString(R.string.backup_to_cloud_key));
cloudBackupPreference.setEnabled(false);
cloudBackupPreference.setChecked(false);
cloudBackupPreference.setSummaryOff(R.string.settings_not_available_summary);
}
// Add actions to the backup preferences
Preference backupNowPreference = findPreference(getString(R.string.backup_to_sd_key));
Preference restoreNowPreference = findPreference(getString(R.string.restore_from_sd_key));
backupNowPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
@@ -120,8 +113,6 @@ public class MyTracksSettings extends PreferenceActivity {
return true;
}
});
Preference restoreNowPreference = findPreference(getString(R.string.restore_from_sd_key));
restoreNowPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
@@ -1,321 +0,0 @@
/*
* 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 com.google.android.maps.mytracks.R;
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 {
SharedPreferences preferences = this.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
if (!preferences.getBoolean(getString(R.string.backup_to_cloud_key), true)) {
Log.i(MyTracksConstants.TAG, "Cloud backup disabled - not doing it.");
return;
}
// 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, preferences);
backupTracks(data, stateManager);
stateManager.flushNewState();
}
protected BackupStateManager createStateManager(ParcelFileDescriptor oldState,
ParcelFileDescriptor newState) {
return new BackupStateManager(oldState, newState);
}
private void backupPreferences(BackupDataOutput data, SharedPreferences preferences) throws IOException {
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,40 @@
/*
* 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.MyTracksSettings;
import android.app.backup.BackupAgentHelper;
import android.app.backup.SharedPreferencesBackupHelper;
/**
* Backup agent helper for mytracks.
*
* TODO: If/when possible, back up the database as well
* (see past revisions in rdamazio-mytracks-staging1 for the code)
*
* @author Rodrigo Damazio
*/
public class MyTracksBackupAgentHelper extends BackupAgentHelper {
@Override
public void onCreate() {
super.onCreate();
addHelper("prefs",
new SharedPreferencesBackupHelper(
this, MyTracksSettings.SETTINGS_NAME));
}
}
@@ -1,112 +0,0 @@
/*
* 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.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory;
import com.google.android.apps.mytracks.testing.TestingProviderUtilsFactory;
import com.google.android.testing.mocking.AndroidMock;
import com.google.android.testing.mocking.UsesMocks;
import android.os.ParcelFileDescriptor;
import junit.framework.TestCase;
/**
* Tests for {@link MyTracksBackupAgent}.
*
* @author Rodrigo Damazio
*/
public class MyTracksBackupAgentTest extends TestCase {
// Database dumpers
private DatabaseDumper trackDumper;
private DatabaseDumper waypointDumper;
private DatabaseDumper pointDumper;
// Database importers
private DatabaseImporter trackImporter;
private DatabaseImporter waypointImporter;
private DatabaseImporter pointImporter;
// Other dependencies
private BackupStateManager stateManager;
private PreferenceBackupHelper preferencesHelper;
private MyTracksProviderUtils providerUtils;
private Factory oldProviderUtilsFactory;
/**
* Testable version of the backup agent, with dependencies mocked out.
*/
private class TestableBackupAgent extends MyTracksBackupAgent {
@Override
protected BackupStateManager createStateManager(
ParcelFileDescriptor oldState, ParcelFileDescriptor newState) {
return stateManager;
}
@Override
protected PreferenceBackupHelper createPreferenceBackupHelper() {
return preferencesHelper;
}
@Override
protected void ensureDumpers() {
super.trackDumper = MyTracksBackupAgentTest.this.trackDumper;
super.waypointDumper = MyTracksBackupAgentTest.this.waypointDumper;
super.pointDumper = MyTracksBackupAgentTest.this.pointDumper;
}
@Override
protected void ensureImporters() {
super.trackImporter = MyTracksBackupAgentTest.this.trackImporter;
super.waypointImporter = MyTracksBackupAgentTest.this.waypointImporter;
super.pointImporter = MyTracksBackupAgentTest.this.pointImporter;
}
}
@UsesMocks({ DatabaseDumper.class, DatabaseImporter.class })
@Override
protected void setUp() throws Exception {
super.setUp();
trackDumper = AndroidMock.createMock(DatabaseDumper.class);
waypointDumper = AndroidMock.createMock(DatabaseDumper.class);
pointDumper = AndroidMock.createMock(DatabaseDumper.class);
trackImporter = AndroidMock.createMock(DatabaseImporter.class);
waypointImporter = AndroidMock.createMock(DatabaseImporter.class);
pointImporter = AndroidMock.createMock(DatabaseImporter.class);
stateManager = AndroidMock.createMock(BackupStateManager.class);
preferencesHelper = AndroidMock.createMock(PreferenceBackupHelper.class);
providerUtils = AndroidMock.createMock(MyTracksProviderUtils.class);
oldProviderUtilsFactory =
TestingProviderUtilsFactory.installWithInstance(providerUtils);
}
@Override
protected void tearDown() throws Exception {
TestingProviderUtilsFactory.restoreOldFactory(oldProviderUtilsFactory);
super.tearDown();
}
public void testOnBackup() {
fail("Not yet implemented");
}
public void testOnRestore() {
fail("Not yet implemented");
}
}