From abb753d2ee48c9aa82e9c21c8bf87eeef8a3ecc8 Mon Sep 17 00:00:00 2001 From: Tim Su Date: Wed, 28 Jul 2010 20:08:48 -0700 Subject: [PATCH] Backup preferences, backup as a service --- astrid/AndroidManifest.xml | 13 ++ .../com/todoroo/astrid/api/Filter.java | 5 + .../todoroo/astrid/api/FilterCategory.java | 2 + .../todoroo/astrid/api/FilterListItem.java | 5 + .../todoroo/astrid/api/TaskDecoration.java | 2 + .../astrid/backup/BackupPreferences.java | 125 ++++++++++++++++++ .../todoroo/astrid/backup/BackupService.java | 72 +++++----- .../astrid/backup/BackupStartupReceiver.java | 26 ++++ .../astrid/backup/TasksXmlExporter.java | 5 +- .../astrid/locale/LocaleEditAlerts.java | 4 +- ...s_preference.xml => status_preference.xml} | 0 astrid/res/values/strings-backup.xml | 31 +++++ astrid/res/xml/preferences_backup.xml | 26 ++++ astrid/res/xml/preferences_rmilk.xml | 2 +- .../astrid/activity/FilterListActivity.java | 2 + .../astrid/activity/TaskListActivity.java | 2 + .../todoroo/astrid/adapter/FilterAdapter.java | 2 +- 17 files changed, 279 insertions(+), 45 deletions(-) create mode 100644 astrid/plugin-src/com/todoroo/astrid/backup/BackupPreferences.java create mode 100644 astrid/plugin-src/com/todoroo/astrid/backup/BackupStartupReceiver.java rename astrid/res/layout/{rmilk_status_preference.xml => status_preference.xml} (100%) create mode 100644 astrid/res/xml/preferences_backup.xml diff --git a/astrid/AndroidManifest.xml b/astrid/AndroidManifest.xml index 583884b4f..3b1405d94 100644 --- a/astrid/AndroidManifest.xml +++ b/astrid/AndroidManifest.xml @@ -201,6 +201,19 @@ + + + + + + + + + + + + diff --git a/astrid/api-src/com/todoroo/astrid/api/Filter.java b/astrid/api-src/com/todoroo/astrid/api/Filter.java index 83083c6b5..e1ca33811 100644 --- a/astrid/api-src/com/todoroo/astrid/api/Filter.java +++ b/astrid/api-src/com/todoroo/astrid/api/Filter.java @@ -9,6 +9,8 @@ import android.os.Parcelable; import com.todoroo.andlib.sql.QueryTemplate; +import edu.umd.cs.findbugs.annotations.CheckForNull; + /** * A FilterListFilter allows users to display tasks that have * something in common. @@ -36,6 +38,7 @@ public final class Filter extends FilterListItem { *

* e.g "Tasks With Notes" */ + @CheckForNull public String title; /** @@ -53,6 +56,7 @@ public final class Filter extends FilterListItem { * metadata.value = 'b' GROUP BY tasks.id ORDER BY tasks.title" * */ + @CheckForNull public String sqlQuery; /** @@ -61,6 +65,7 @@ public final class Filter extends FilterListItem { * tasks they create should also be tagged 'ABC'. If set to null, no * additional values will be stored for a task. */ + @CheckForNull public ContentValues valuesForNewTasks = null; /** diff --git a/astrid/api-src/com/todoroo/astrid/api/FilterCategory.java b/astrid/api-src/com/todoroo/astrid/api/FilterCategory.java index 3fe51d269..4b00695ad 100644 --- a/astrid/api-src/com/todoroo/astrid/api/FilterCategory.java +++ b/astrid/api-src/com/todoroo/astrid/api/FilterCategory.java @@ -5,6 +5,7 @@ package com.todoroo.astrid.api; import android.os.Parcel; import android.os.Parcelable; +import edu.umd.cs.findbugs.annotations.CheckForNull; /** * A FilterCategory groups common {@link Filter}s and allows @@ -18,6 +19,7 @@ public class FilterCategory extends FilterListItem { /** * {@link Filter}s contained by this category */ + @CheckForNull public Filter[] children; /** diff --git a/astrid/api-src/com/todoroo/astrid/api/FilterListItem.java b/astrid/api-src/com/todoroo/astrid/api/FilterListItem.java index 747a22cf3..d23f9e4e9 100644 --- a/astrid/api-src/com/todoroo/astrid/api/FilterListItem.java +++ b/astrid/api-src/com/todoroo/astrid/api/FilterListItem.java @@ -7,6 +7,7 @@ import android.content.Intent; import android.graphics.Bitmap; import android.os.Parcel; import android.os.Parcelable; +import edu.umd.cs.findbugs.annotations.CheckForNull; /** * Represents an item displayed by Astrid's FilterListActivity @@ -19,17 +20,20 @@ abstract public class FilterListItem implements Parcelable { /** * Title of this item displayed on the Filters page */ + @CheckForNull public String listingTitle = null; /** * Bitmap for icon used on listing page. null => no icon */ + @CheckForNull public Bitmap listingIcon = null; /** * Context Menu labels. The context menu will be displayed when users * long-press on this filter list item. */ + @CheckForNull public String contextMenuLabels[] = new String[0]; /** @@ -37,6 +41,7 @@ abstract public class FilterListItem implements Parcelable { * content menu label is invoked. This array must be the same size as * the contextMenuLabels array. */ + @CheckForNull public Intent contextMenuIntents[] = new Intent[0]; // --- parcelable helpers diff --git a/astrid/api-src/com/todoroo/astrid/api/TaskDecoration.java b/astrid/api-src/com/todoroo/astrid/api/TaskDecoration.java index e1cf4115c..3d12aab10 100644 --- a/astrid/api-src/com/todoroo/astrid/api/TaskDecoration.java +++ b/astrid/api-src/com/todoroo/astrid/api/TaskDecoration.java @@ -7,6 +7,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.widget.RemoteViews; import android.widget.RemoteViews.RemoteView; +import edu.umd.cs.findbugs.annotations.CheckForNull; /** * Represents a line of text displayed in the Task List @@ -29,6 +30,7 @@ public final class TaskDecoration implements Parcelable { /** * {@link RemoteView} decoration */ + @CheckForNull public RemoteViews decoration = null; /** diff --git a/astrid/plugin-src/com/todoroo/astrid/backup/BackupPreferences.java b/astrid/plugin-src/com/todoroo/astrid/backup/BackupPreferences.java new file mode 100644 index 000000000..1ca13d244 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/backup/BackupPreferences.java @@ -0,0 +1,125 @@ +package com.todoroo.astrid.backup; + +import java.util.Date; + +import android.content.res.Resources; +import android.graphics.Color; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceClickListener; +import android.view.View; +import android.view.ViewGroup.OnHierarchyChangeListener; + +import com.timsu.astrid.R; +import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.utility.DateUtilities; +import com.todoroo.andlib.utility.DialogUtilities; +import com.todoroo.andlib.widget.TodorooPreferences; +import com.todoroo.astrid.utility.Preferences; + +/** + * Displays synchronization preferences and an action panel so users can + * initiate actions from the menu. + * + * @author timsu + * + */ +public class BackupPreferences extends TodorooPreferences { + + static final String PREF_BACKUP_LAST_DATE = "backupDate"; //$NON-NLS-1$ + + static final String PREF_BACKUP_LAST_ERROR = "backupError"; //$NON-NLS-1$ + + @Autowired + private DialogUtilities dialogUtilities; + + private int statusColor = Color.BLACK; + + @Override + public int getPreferenceResource() { + return R.xml.preferences_backup; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getListView().setOnHierarchyChangeListener(new OnHierarchyChangeListener() { + + @Override + public void onChildViewRemoved(View parent, View child) { + // + } + + @Override + public void onChildViewAdded(View parent, View child) { + View view = findViewById(R.id.status); + if(view != null) + view.setBackgroundColor(statusColor); + } + }); + } + + @Override + protected void onPause() { + super.onPause(); + BackupService.scheduleService(this); + } + + /** + * + * @param resource + * if null, updates all resources + */ + @Override + public void updatePreferences(Preference preference, Object value) { + final Resources r = getResources(); + + // auto + if (r.getString(R.string.backup_BPr_auto_key).equals( + preference.getKey())) { + if (value != null && !(Boolean)value) + preference.setSummary(R.string.backup_BPr_auto_disabled); + else + preference.setSummary(R.string.backup_BPr_auto_enabled); + } + + // status + else if (r.getString(R.string.backup_BPr_status_key).equals(preference.getKey())) { + String status; + String subtitle = ""; //$NON-NLS-1$ + + // last backup was error + final long last = Preferences.getLong(PREF_BACKUP_LAST_DATE, 0); + final String error = Preferences.getStringValue(PREF_BACKUP_LAST_ERROR); + if(error != null) { + status = r.getString(R.string.backup_status_failed); + subtitle = r.getString(R.string.backup_status_failed_subtitle); + statusColor = Color.rgb(100, 0, 0); + preference.setOnPreferenceClickListener(new OnPreferenceClickListener() { + public boolean onPreferenceClick(Preference p) { + dialogUtilities.okDialog(BackupPreferences.this, error, null); + return true; + } + }); + } else if(last > 0) { + status = r.getString(R.string.backup_status_success, + DateUtilities.getDateWithTimeFormat(BackupPreferences.this). + format(new Date(last))); + statusColor = Color.rgb(0, 100, 0); + preference.setOnPreferenceClickListener(null); + } else { + status = r.getString(R.string.backup_status_never); + statusColor = Color.rgb(0, 0, 100); + preference.setOnPreferenceClickListener(null); + } + preference.setTitle(status); + preference.setSummary(subtitle); + + View view = findViewById(R.id.status); + if(view != null) + view.setBackgroundColor(statusColor); + } + + } + +} \ No newline at end of file diff --git a/astrid/plugin-src/com/todoroo/astrid/backup/BackupService.java b/astrid/plugin-src/com/todoroo/astrid/backup/BackupService.java index e62a6bd4d..29cbad18c 100644 --- a/astrid/plugin-src/com/todoroo/astrid/backup/BackupService.java +++ b/astrid/plugin-src/com/todoroo/astrid/backup/BackupService.java @@ -13,11 +13,16 @@ import android.content.Intent; import android.os.IBinder; import android.util.Log; +import com.timsu.astrid.R; +import com.todoroo.astrid.utility.Preferences; + /** * Inspired heavily by SynchronizationService */ public class BackupService extends Service { + // --- constants for backup + /** * when after phone starts to start first back up */ @@ -27,13 +32,10 @@ public class BackupService extends Service { * how often to back up */ private static final long BACKUP_INTERVAL = AlarmManager.INTERVAL_DAY; - public static final String BACKUP_ACTION = "backup"; - public static final String BACKUP_FILE_NAME_REGEX = "auto\\.[-\\d]+\\.xml"; + public static final String BACKUP_ACTION = "backup"; //$NON-NLS-1$ + public static final String BACKUP_FILE_NAME_REGEX = "auto\\.[-\\d]+\\.xml"; //$NON-NLS-1$ private static final int DAYS_TO_KEEP_BACKUP = 7; - static final String PREF_BACKUP_LAST_ERROR = "backupError"; - static final String PREF_BACKUP_LAST_DATE = "backupDate"; - @Override public IBinder onBind(Intent intent) { return null; @@ -48,66 +50,56 @@ public class BackupService extends Service { /** * Test hook for backup - * @param ctx + * @param context */ - public void testBackup(Context ctx) { - startBackup(ctx); + public void testBackup(Context context) { + startBackup(context); } - private void startBackup(Context ctx) { - /*if (ctx == null || ctx.getResources() == null) { + private void startBackup(Context context) { + if (context == null || context.getResources() == null) { return; } try { - if (!Preferences.isBackupEnabled(ctx)) { + if (!Preferences.getBoolean(R.string.backup_BPr_auto_key, true)) { return; } try { deleteOldBackups(); } catch (Exception e) { - Log.e("error-deleting", "Error deleting old backups: " + e); + Log.e("error-deleting", "Error deleting old backups", e); //$NON-NLS-1$ //$NON-NLS-2$ } - TasksXmlExporter exporter = new TasksXmlExporter(true); - exporter.setContext(ctx); - exporter.exportTasks(backupDirectorySetting.getBackupDirectory()); - Preferences.setBackupSummary(ctx, - ctx.getString(R.string.BPr_backup_desc_success, - BackupDateUtilities.getFormattedDate(ctx, new Date()))); + + TasksXmlExporter.exportTasks(context, true, null); + } catch (Exception e) { - // unable to backup. - if (e == null || e.getMessage() == null) { - Preferences.setBackupSummary(ctx, - ctx.getString(R.string.BPr_backup_desc_failure_null)); - } else { - Preferences.setBackupSummary(ctx, - ctx.getString(R.string.BPr_backup_desc_failure, - e.toString())); - } - }*/ + Log.e("error-backup", "Error starting backups", e); //$NON-NLS-1$ //$NON-NLS-2$ + Preferences.setString(BackupPreferences.PREF_BACKUP_LAST_ERROR, e.toString()); + } } - public static void scheduleService(Context ctx) { - /*AlarmManager am = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE); - PendingIntent pendingIntent = PendingIntent.getService(ctx, 0, - createAlarmIntent(ctx), PendingIntent.FLAG_UPDATE_CURRENT); + public static void scheduleService(Context context) { + AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + PendingIntent pendingIntent = PendingIntent.getService(context, 0, + createAlarmIntent(context), PendingIntent.FLAG_UPDATE_CURRENT); am.cancel(pendingIntent); - if (!Preferences.isBackupEnabled(ctx)) { + if (!Preferences.getBoolean(R.string.backup_BPr_auto_key, true)) { return; } am.setInexactRepeating(AlarmManager.RTC, System.currentTimeMillis() + BACKUP_OFFSET, - BACKUP_INTERVAL, pendingIntent);*/ + BACKUP_INTERVAL, pendingIntent); } - public static void unscheduleService(Context ctx) { - AlarmManager am = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE); - PendingIntent pendingIntent = PendingIntent.getService(ctx, 0, - createAlarmIntent(ctx), PendingIntent.FLAG_UPDATE_CURRENT); + public static void unscheduleService(Context context) { + AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + PendingIntent pendingIntent = PendingIntent.getService(context, 0, + createAlarmIntent(context), PendingIntent.FLAG_UPDATE_CURRENT); am.cancel(pendingIntent); } - private static Intent createAlarmIntent(Context ctx) { - Intent intent = new Intent(ctx, BackupService.class); + private static Intent createAlarmIntent(Context context) { + Intent intent = new Intent(context, BackupService.class); intent.setAction(BACKUP_ACTION); return intent; } diff --git a/astrid/plugin-src/com/todoroo/astrid/backup/BackupStartupReceiver.java b/astrid/plugin-src/com/todoroo/astrid/backup/BackupStartupReceiver.java new file mode 100644 index 000000000..d19286a9d --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/backup/BackupStartupReceiver.java @@ -0,0 +1,26 @@ +/** + * See the file "LICENSE" for the full license governing this code. + */ +package com.todoroo.astrid.backup; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.todoroo.andlib.service.ContextManager; +import com.todoroo.astrid.service.AstridDependencyInjector; + +public class BackupStartupReceiver extends BroadcastReceiver { + + static { + AstridDependencyInjector.initialize(); + } + + @Override + /** Called when device is restarted */ + public void onReceive(final Context context, Intent intent) { + ContextManager.setContext(context); + BackupService.scheduleService(context); + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/backup/TasksXmlExporter.java b/astrid/plugin-src/com/todoroo/astrid/backup/TasksXmlExporter.java index aac2f93fc..53ef05261 100644 --- a/astrid/plugin-src/com/todoroo/astrid/backup/TasksXmlExporter.java +++ b/astrid/plugin-src/com/todoroo/astrid/backup/TasksXmlExporter.java @@ -91,8 +91,9 @@ public class TasksXmlExporter { String output = setupFile(BackupConstants.getExportDirectory(), isService); doTasksExport(output); - Preferences.setLong(BackupService.PREF_BACKUP_LAST_DATE, + Preferences.setLong(BackupPreferences.PREF_BACKUP_LAST_DATE, DateUtilities.now()); + Preferences.setString(BackupPreferences.PREF_BACKUP_LAST_ERROR, null); if (!isService) displayToast(output); @@ -102,7 +103,7 @@ public class TasksXmlExporter { context.getString(R.string.backup_TXI_error), e); else { exceptionService.reportError("background-backup", e); //$NON-NLS-1$ - Preferences.setString(BackupService.PREF_BACKUP_LAST_ERROR, e.toString()); + Preferences.setString(BackupPreferences.PREF_BACKUP_LAST_ERROR, e.toString()); } } finally { if(runAfterExport != null) diff --git a/astrid/plugin-src/com/todoroo/astrid/locale/LocaleEditAlerts.java b/astrid/plugin-src/com/todoroo/astrid/locale/LocaleEditAlerts.java index 8bcc9744a..220628589 100644 --- a/astrid/plugin-src/com/todoroo/astrid/locale/LocaleEditAlerts.java +++ b/astrid/plugin-src/com/todoroo/astrid/locale/LocaleEditAlerts.java @@ -154,6 +154,8 @@ public final class LocaleEditAlerts extends ExpandableListActivity { adapter.setSelection(item); } else if(item instanceof FilterCategory) { Filter[] filters = ((FilterCategory)item).children; + if(filters == null) + return; for(Filter filter : filters) if(finalSelection.equals(filter.sqlQuery)) { adapter.setSelection(filter); @@ -243,7 +245,7 @@ public final class LocaleEditAlerts extends ExpandableListActivity { /* * This is the blurb concisely describing what your setting's state is. This is simply used for display in the UI. */ - if (filterItem.title.length() > com.twofortyfouram.Intent.MAXIMUM_BLURB_LENGTH) + if (filterItem.title != null && filterItem.title.length() > com.twofortyfouram.Intent.MAXIMUM_BLURB_LENGTH) returnIntent.putExtra(com.twofortyfouram.Intent.EXTRA_STRING_BLURB, filterItem.title.substring(0, com.twofortyfouram.Intent.MAXIMUM_BLURB_LENGTH)); else returnIntent.putExtra(com.twofortyfouram.Intent.EXTRA_STRING_BLURB, filterItem.title); diff --git a/astrid/res/layout/rmilk_status_preference.xml b/astrid/res/layout/status_preference.xml similarity index 100% rename from astrid/res/layout/rmilk_status_preference.xml rename to astrid/res/layout/status_preference.xml diff --git a/astrid/res/values/strings-backup.xml b/astrid/res/values/strings-backup.xml index 2fd48d9dc..fd6008da0 100644 --- a/astrid/res/values/strings-backup.xml +++ b/astrid/res/values/strings-backup.xml @@ -20,6 +20,37 @@ Latest backup was on %s + + + + Backups + + + Status + + backup_status + + + Latest: %s + + Last Backup Failed + + (tap to show error) + + Never Backed Up! + + + Options + + + Automatic Backups + + Automatic Backups Disabled + + Backup will occur daily + + backup + diff --git a/astrid/res/xml/preferences_backup.xml b/astrid/res/xml/preferences_backup.xml new file mode 100644 index 000000000..4e38116df --- /dev/null +++ b/astrid/res/xml/preferences_backup.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/astrid/res/xml/preferences_rmilk.xml b/astrid/res/xml/preferences_rmilk.xml index d1e22544b..bb9ba2f88 100644 --- a/astrid/res/xml/preferences_rmilk.xml +++ b/astrid/res/xml/preferences_rmilk.xml @@ -6,7 +6,7 @@ android:title="@string/rmilk_MPr_group_status"> diff --git a/astrid/src/com/todoroo/astrid/activity/FilterListActivity.java b/astrid/src/com/todoroo/astrid/activity/FilterListActivity.java index 181797227..be75863ee 100644 --- a/astrid/src/com/todoroo/astrid/activity/FilterListActivity.java +++ b/astrid/src/com/todoroo/astrid/activity/FilterListActivity.java @@ -336,6 +336,8 @@ public class FilterListActivity extends ExpandableListActivity { FrameLayout frameLayout = new FrameLayout(this); frameLayout.setPadding(10, 0, 10, 0); final EditText editText = new EditText(this); + if(filter.listingTitle == null) + filter.listingTitle = ""; //$NON-NLS-1$ editText.setText(filter.listingTitle. replaceAll("\\(\\d+\\)$", "").trim()); //$NON-NLS-1$ //$NON-NLS-2$ frameLayout.addView(editText, new FrameLayout.LayoutParams( diff --git a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java index 00337e327..afbc79173 100644 --- a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java +++ b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java @@ -596,6 +596,8 @@ public class TaskListActivity extends ListActivity implements OnScrollListener { } // create a custom cursor + if(filter.sqlQuery == null) + filter.sqlQuery = ""; if(!filter.sqlQuery.contains("WHERE")) filter.sqlQuery += " WHERE " + TaskCriteria.byId(withCustomId); else diff --git a/astrid/src/com/todoroo/astrid/adapter/FilterAdapter.java b/astrid/src/com/todoroo/astrid/adapter/FilterAdapter.java index 2751d295f..7d8702299 100644 --- a/astrid/src/com/todoroo/astrid/adapter/FilterAdapter.java +++ b/astrid/src/com/todoroo/astrid/adapter/FilterAdapter.java @@ -110,7 +110,7 @@ public class FilterAdapter extends BaseExpandableListAdapter { public Object getChild(int groupPosition, int childPosition) { FilterListItem item = items.get(groupPosition); - if(!(item instanceof FilterCategory)) + if(!(item instanceof FilterCategory) || ((FilterCategory)item).children == null) return null; return ((FilterCategory)item).children[childPosition];