Merge with Corey Downing's backup branch

------------------------------------------------------------
Use --include-merges or -n0 to see merged revisions.
pull/14/head
Tim Su 16 years ago
parent 1cfab5670e
commit 74ab94f6fb

@ -1,10 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry excluding="javax/xml/validation/|javax/xml/transform/dom/|javax/xml/parsers/|org/jaxp/transform/dom/|org/jaxp/transform/sax/|org/jaxp/transform/stax/|org/jaxp/transform/stream/|org/jaxp/stream/events/|org/jaxp/stream/util/|org/jaxp/parsers/|org/jaxp/stream/|org/jaxp/validation/" kind="src" path="src"/>
<classpathentry kind="lib" path="lib/commons-codec-1.3.jar"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="lib" path="lib/commons-codec-1.3.jar"/>
<classpathentry kind="lib" path="lib/locale_platform.jar"/>
<classpathentry kind="lib" path="lib/FlurryAgent.jar"/>
<classpathentry kind="output" path="ecbuild"/>
<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Android 1.6"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Python 2.6.4 interpreter library"/>
<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Android 1.6"/>
<classpathentry kind="output" path="out/production/astrid-2.x"/>
</classpath>

@ -10,6 +10,7 @@
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- For Flurry analytics -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
@ -119,6 +120,7 @@
<service android:name=".appwidget.AstridAppWidgetProvider$UpdateService"></service>
<service android:name=".sync.SynchronizationService" />
<service android:name=".utilities.BackupService"/>
<!-- ======================== Providers ========================== -->

@ -48,6 +48,8 @@
<string name="p_nagging">nagging</string>
<string name="p_deadlineTime">deadline_time</string>
<string name="p_backup">backup</string>
<string name="prefs_titleVisible">titleVisible</string>
<string name="prefs_titleVisible_default">true</string>

@ -175,7 +175,9 @@
<string name="taskList_menu_survey">Take Astrid\'s Survey!</string>
<string name="taskList_menu_tips">Quick Tips</string>
<string name="taskList_menu_cleanup">Clean Up Old Tasks</string>
<string name="taskList_menu_export">Export Tasks</string>
<string name="taskList_menu_import">Import Tasks</string>
<string name="taskList_context_edit">Edit Task</string>
<string name="taskList_context_delete">Delete Task</string>
<string name="taskList_context_startTimer">Start Timer</string>
@ -379,7 +381,24 @@ Astrid might not let you know when your tasks are due.\n
</string>
<string name="task_killer_help_ok">I Won\'t Kill Astrid!</string>
<!-- Locale Plugin -->
<!-- Import/Export -->
<skip />
<string name="export_toast">Exported %s to %s.</string>
<string name="import_summary_title">Import Summary</string>
<string name="import_summary_message">
File %s contained %d tasks.\n
Imported %d tasks.\n
Skipped %d tasks.\n
</string>
<string name="import_progress_title">Import</string>
<string name="import_progress_open">Opening file...</string>
<string name="import_progress_opened">File opened...</string>
<string name="import_progress_read">Reading task %d...</string>
<string name="import_progress_skip">Skipped task %d...</string>
<string name="import_progress_add">Imported task %d...</string>
<string name="import_file_prompt">Select a File to Import</string>
<!-- Locale Plugin -->
<skip />
<string name="locale_edit_alerts_title">Astrid Tag Alert</string>
@ -401,6 +420,7 @@ Astrid might not let you know when your tasks are due.\n
<string name="error_opening">Couldn't find this item:</string>
<string name="error_saving">Couldn't save:</string>
<string name="error_sdcard">Cannot access:</string>
<skip />
@ -442,6 +462,9 @@ Astrid might not let you know when your tasks are due.\n
<string name="prefs_deadlineTime_title">Default Deadlines</string>
<string name="prefs_deadlineTime_desc"># of days from now to set new deadlines</string>
<string name="prefs_backup_title">Automatic Backups</string>
<string name="prefs_backup_desc">Perform daily backups to sdcard.</string>
<string name="displayedFields_PrefScreen_Title">Displayed Fields</string>
<string name="displayedFields_PrefScreen_Desc">Select the fields to show in task list</string>

@ -107,6 +107,11 @@
<EditTextPreference
android:key="@string/p_deadlineTime"
android:title="@string/prefs_deadlineTime_title"
android:summary="@string/prefs_deadlineTime_desc" />
android:summary="@string/prefs_deadlineTime_desc" />
<CheckBoxPreference
android:key="@string/p_backup"
android:title="@string/prefs_backup_title"
android:summary="@string/prefs_backup_desc"
android:defaultValue="true" />
</PreferenceCategory>
</PreferenceScreen>

@ -19,15 +19,6 @@
*/
package com.timsu.astrid.activities;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
@ -40,24 +31,12 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.*;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnClickListener;
import android.view.View.OnCreateContextMenuListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.*;
import android.widget.AdapterView.OnItemClickListener;
import com.flurry.android.FlurryAgent;
import com.timsu.astrid.R;
import com.timsu.astrid.activities.TaskListAdapter.TaskListAdapterHooks;
@ -71,16 +50,15 @@ import com.timsu.astrid.data.task.TaskModelForList;
import com.timsu.astrid.sync.SynchronizationService;
import com.timsu.astrid.sync.Synchronizer;
import com.timsu.astrid.sync.Synchronizer.SynchronizerListener;
import com.timsu.astrid.utilities.AstridUtilities;
import com.timsu.astrid.utilities.Constants;
import com.timsu.astrid.utilities.DialogUtilities;
import com.timsu.astrid.utilities.Notifications;
import com.timsu.astrid.utilities.Preferences;
import com.timsu.astrid.utilities.*;
import com.timsu.astrid.widget.FilePickerBuilder;
import com.timsu.astrid.widget.NNumberPickerDialog.OnNNumberPickedListener;
import com.timsu.astrid.widget.NumberPicker;
import com.timsu.astrid.widget.NumberPickerDialog;
import com.timsu.astrid.widget.NNumberPickerDialog.OnNNumberPickedListener;
import com.timsu.astrid.widget.NumberPickerDialog.OnNumberPickedListener;
import java.util.*;
/**
* Primary view for the Astrid Application. Lists all of the tasks in the
* system, and allows users to interact with them.
@ -116,6 +94,8 @@ public class TaskListSubActivity extends SubActivity {
private static final int OPTIONS_HELP_ID = Menu.FIRST + 12;
private static final int OPTIONS_CLEANUP_ID = Menu.FIRST + 13;
private static final int OPTIONS_QUICK_TIPS = Menu.FIRST + 14;
private static final int OPTIONS_EXPORT = Menu.FIRST + 15;
private static final int OPTIONS_IMPORT = Menu.FIRST + 16;
private static final int CONTEXT_FILTER_HIDDEN = Menu.FIRST + 20;
private static final int CONTEXT_FILTER_DONE = Menu.FIRST + 21;
@ -384,6 +364,12 @@ public class TaskListSubActivity extends SubActivity {
item = menu.add(Menu.NONE, OPTIONS_QUICK_TIPS, Menu.NONE,
R.string.taskList_menu_tips);
item = menu.add(Menu.NONE, OPTIONS_EXPORT, Menu.NONE,
R.string.taskList_menu_export);
item = menu.add(Menu.NONE, OPTIONS_IMPORT, Menu.NONE,
R.string.taskList_menu_import);
item = menu.add(Menu.NONE, OPTIONS_HELP_ID, Menu.NONE,
R.string.taskList_menu_help);
item.setAlphabeticShortcut('h');
@ -1230,6 +1216,12 @@ public class TaskListSubActivity extends SubActivity {
case OPTIONS_CLEANUP_ID:
cleanOldTasks();
return true;
case OPTIONS_EXPORT:
exportTasks();
return true;
case OPTIONS_IMPORT:
importTasks();
return true;
// --- list context menu items
case TaskListAdapter.CONTEXT_EDIT_ID:
@ -1297,6 +1289,33 @@ public class TaskListSubActivity extends SubActivity {
return false;
}
private void importTasks() {
final Runnable reloadList = new Runnable() {
public void run() {
reloadList();
}
};
final Context ctx = this.getParent();
FilePickerBuilder.OnFilePickedListener listener = new FilePickerBuilder.OnFilePickedListener() {
@Override
public void onFilePicked(String filePath) {
TasksXmlImporter importer = new TasksXmlImporter(ctx);
importer.setInput(filePath);
importer.importTasks(reloadList);
}
};
DialogUtilities.filePicker(ctx,
ctx.getString(R.string.import_file_prompt),
TasksXmlExporter.getExportDirectory(),
listener);
}
private void exportTasks() {
TasksXmlExporter exporter = new TasksXmlExporter(false);
exporter.setContext(getParent());
exporter.exportTasks();
}
/*
* ======================================================================
* ===================================================== getters / setters

@ -19,21 +19,20 @@
*/
package com.timsu.astrid.data.sync;
import java.util.HashSet;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.timsu.astrid.data.AbstractController;
import com.timsu.astrid.data.sync.SyncMapping.SyncMappingDatabaseHelper;
import com.timsu.astrid.data.task.AbstractTaskModel;
import com.timsu.astrid.data.task.TaskIdentifier;
import com.timsu.astrid.data.task.TaskModelForSync;
import java.util.HashSet;
/** Controller for Tag-related operations */
public class SyncDataController extends AbstractController {
@ -91,6 +90,30 @@ public class SyncDataController extends AbstractController {
}
}
/** Get all mappings for specified task for all synchronization services */
public HashSet<SyncMapping> getSyncMappings(TaskIdentifier taskId)
throws SQLException {
HashSet<SyncMapping> list = new HashSet<SyncMapping>();
Cursor cursor = syncDatabase.query(SYNC_TABLE_NAME,
SyncMapping.FIELD_LIST,
SyncMapping.TASK + " = ?",
new String[] { "" + taskId.getId() },
null, null, null);
try {
if(cursor.getCount() == 0)
return list;
do {
cursor.moveToNext();
list.add(new SyncMapping(cursor));
} while(!cursor.isLast());
return list;
} finally {
cursor.close();
}
}
/** Get mapping for given task */
public SyncMapping getSyncMapping(int syncServiceId, TaskIdentifier taskId)
throws SQLException {

@ -19,23 +19,22 @@
*/
package com.timsu.astrid.data.tag;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import com.timsu.astrid.data.AbstractController;
import com.timsu.astrid.data.tag.AbstractTagModel.TagModelDatabaseHelper;
import com.timsu.astrid.data.tag.TagToTaskMapping.TagToTaskMappingDatabaseHelper;
import com.timsu.astrid.data.task.TaskIdentifier;
import com.timsu.astrid.data.task.AbstractTaskModel.TaskModelDatabaseHelper;
import com.timsu.astrid.data.task.TaskIdentifier;
import com.timsu.astrid.provider.TasksProvider;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
/** Controller for Tag-related operations */
public class TagController extends AbstractController {

@ -19,11 +19,6 @@
*/
package com.timsu.astrid.data.task;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
@ -35,7 +30,6 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.util.Log;
import com.timsu.astrid.activities.TaskEdit;
import com.timsu.astrid.activities.TaskListSubActivity;
import com.timsu.astrid.appwidget.AstridAppWidgetProvider.UpdateService;
@ -49,6 +43,11 @@ import com.timsu.astrid.sync.Synchronizer;
import com.timsu.astrid.sync.Synchronizer.SynchronizerListener;
import com.timsu.astrid.utilities.Notifications;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
/**
* Controller for task-related operations
*
@ -124,6 +123,14 @@ public class TaskController extends AbstractController {
null, null, null, null, null, null);
}
/** Return a list of all tasks */
public Cursor getBackupTaskListCursor() {
return database.query(TASK_TABLE_NAME, TaskModelForXml.FIELD_LIST,
AbstractTaskModel.PROGRESS_PERCENTAGE + " < " +
AbstractTaskModel.COMPLETE_PERCENTAGE, null, null, null,
null, null);
}
/** Delete all completed tasks with date < older than date */
public int deleteCompletedTasksOlderThan(Date olderThanDate) {
return database.delete(TASK_TABLE_NAME, String.format("`%s` >= '%d' AND `%s` <= '%d'",
@ -434,6 +441,31 @@ public class TaskController extends AbstractController {
return model;
}
/** Returns a TaskModelForXml corresponding to the given TaskIdentifier */
public TaskModelForXml fetchTaskForXml(TaskIdentifier taskId) throws SQLException {
Cursor cursor = fetchTaskCursor(taskId, TaskModelForXml.FIELD_LIST);
TaskModelForXml model = new TaskModelForXml(cursor);
cursor.close();
return model;
}
/* Attempts to return a TaskModelForXml for the given name and creation date */
public TaskModelForXml fetchTaskForXml(String name, Date creationDate) {
Cursor cursor;
try {
cursor = fetchTaskCursor(name, "" + creationDate.getTime(),
TaskModelForXml.FIELD_LIST);
} catch (SQLException e) {
return null;
}
if (cursor == null || cursor.getCount() == 0) {
return null;
}
TaskModelForXml model = new TaskModelForXml(cursor);
cursor.close();
return model;
}
/** Returns a TaskModelForReminder corresponding to the given TaskIdentifier */
public TaskModelForReminder fetchTaskForReminder(TaskIdentifier taskId) throws SQLException {
Cursor cursor = fetchTaskCursor(taskId, TaskModelForReminder.FIELD_LIST);
@ -490,6 +522,21 @@ public class TaskController extends AbstractController {
return cursor;
}
/** Returns null if unsuccessful, otherwise moves cursor to the task.
* Don't forget to close the cursor when you're done. */
private Cursor fetchTaskCursor(String name, String creationDate, String[] fieldList) {
final String where = AbstractTaskModel.NAME + " = ? AND "
+ AbstractTaskModel.CREATION_DATE + " = ?";
Cursor cursor = database.query(true, TASK_TABLE_NAME, fieldList,
where, new String[] {name, creationDate}, null, null, null, null);
if (cursor == null)
throw new SQLException("Returned empty set!");
if (cursor.moveToFirst()) {
return cursor;
}
return null;
}
// --- methods supporting individual features
/** Returns a TaskModelForView corresponding to the given TaskIdentifier */

@ -0,0 +1,192 @@
package com.timsu.astrid.data.task;
import android.database.Cursor;
import com.timsu.astrid.data.AbstractController;
import com.timsu.astrid.data.enums.Importance;
import com.timsu.astrid.data.enums.RepeatInterval;
import com.timsu.astrid.utilities.DateUtilities;
import java.util.Date;
import java.util.HashMap;
public class TaskModelForXml extends AbstractTaskModel {
static String[] FIELD_LIST = new String[] {
AbstractController.KEY_ROWID,
NAME,
IMPORTANCE,
ELAPSED_SECONDS,
ESTIMATED_SECONDS,
TIMER_START,
DEFINITE_DUE_DATE,
PREFERRED_DUE_DATE,
NOTIFICATIONS,
PROGRESS_PERCENTAGE,
COMPLETION_DATE,
CREATION_DATE,
HIDDEN_UNTIL,
NOTES,
REPEAT,
FLAGS,
POSTPONE_COUNT,
BLOCKING_ON,
LAST_NOTIFIED,
NOTIFICATION_FLAGS,
CALENDAR_URI,
};
private HashMap<String, String> taskAttributesMap;
public static final String REPEAT_VALUE = "repeat_value";
public static final String REPEAT_INTERVAL = "repeat_interval";
private RepeatInterval repeatInterval = null;
private Integer repeatValue = null;
// --- constructors
public TaskModelForXml() {
super();
setCreationDate(new Date());
taskAttributesMap = new HashMap<String, String>(FIELD_LIST.length);
}
public TaskModelForXml(Cursor cursor) {
super(cursor);
prefetchData(FIELD_LIST);
taskAttributesMap = new HashMap<String, String>(FIELD_LIST.length);
}
/* Safely add a value from a date field (in case of null values) to the
taskAttributesMap.
*/
private void safePutDate(String field, Date value) {
if (value != null) {
taskAttributesMap.put(field, DateUtilities.getIso8601String(value));
}
}
// --- getters and setters
public Date getCreationDate() {
return super.getCreationDate();
}
/* Build a HashMap of task fields and associated values.
*/
public HashMap<String, String> getTaskAttributes() {
taskAttributesMap.put(AbstractController.KEY_ROWID, getTaskIdentifier().idAsString());
taskAttributesMap.put(NAME, getName());
taskAttributesMap.put(IMPORTANCE, getImportance().toString());
taskAttributesMap.put(ELAPSED_SECONDS, getElapsedSeconds().toString());
taskAttributesMap.put(ESTIMATED_SECONDS, getEstimatedSeconds().toString());
safePutDate(TIMER_START, getTimerStart());
safePutDate(DEFINITE_DUE_DATE, getDefiniteDueDate());
safePutDate(PREFERRED_DUE_DATE, getPreferredDueDate());
taskAttributesMap.put(NOTIFICATIONS, getNotificationIntervalSeconds().toString());
taskAttributesMap.put(PROGRESS_PERCENTAGE, Integer.toString(getProgressPercentage()));
safePutDate(COMPLETION_DATE, getCompletionDate());
safePutDate(CREATION_DATE, getCreationDate());
safePutDate(HIDDEN_UNTIL, getHiddenUntil());
taskAttributesMap.put(NOTES, getNotes());
RepeatInfo repeat = getRepeat();
if (repeat != null) {
taskAttributesMap.put(REPEAT_VALUE, Integer.toString(repeat.getValue()));
taskAttributesMap.put(REPEAT_INTERVAL,
Integer.toString(repeat.getInterval().getLabelResource()));
}
taskAttributesMap.put(FLAGS, Integer.toString(getFlags()));
taskAttributesMap.put(POSTPONE_COUNT, getPostponeCount().toString());
taskAttributesMap.put(BLOCKING_ON, Long.toString(getBlockingOn().getId()));
safePutDate(LAST_NOTIFIED, getLastNotificationDate());
taskAttributesMap.put(NOTIFICATION_FLAGS, Integer.toString(getNotificationFlags()));
String calendarUri = getCalendarUri();
if (calendarUri != null) {
taskAttributesMap.put(CALENDAR_URI, calendarUri);
}
return taskAttributesMap;
}
// --- setters
public boolean setField(String field, String value) {
boolean success = true;
if(field.equals(NAME)) {
setName(value);
}
else if(field.equals(NOTES)) {
setNotes(value);
}
else if(field.equals(PROGRESS_PERCENTAGE)) {
setProgressPercentage(Integer.parseInt(value));
}
else if(field.equals(IMPORTANCE)) {
setImportance(Importance.valueOf(value));
}
else if(field.equals(ESTIMATED_SECONDS)) {
setEstimatedSeconds(Integer.parseInt(value));
}
else if(field.equals(ELAPSED_SECONDS)) {
setElapsedSeconds(Integer.parseInt(value));
}
else if(field.equals(TIMER_START)) {
setTimerStart(DateUtilities.getDateFromIso8601String(value));
}
else if(field.equals(DEFINITE_DUE_DATE)) {
setDefiniteDueDate(DateUtilities.getDateFromIso8601String(value));
}
else if(field.equals(PREFERRED_DUE_DATE)) {
setPreferredDueDate(DateUtilities.getDateFromIso8601String(value));
}
else if(field.equals(HIDDEN_UNTIL)) {
setHiddenUntil(DateUtilities.getDateFromIso8601String(value));
}
else if(field.equals(BLOCKING_ON)) {
setBlockingOn(new TaskIdentifier(Long.parseLong(value)));
}
else if(field.equals(POSTPONE_COUNT)) {
setPostponeCount(Integer.parseInt(value));
}
else if(field.equals(NOTIFICATIONS)) {
setNotificationIntervalSeconds(Integer.parseInt(value));
}
else if(field.equals(CREATION_DATE)) {
setCreationDate(DateUtilities.getDateFromIso8601String(value));
}
else if(field.equals(COMPLETION_DATE)) {
setCompletionDate(DateUtilities.getDateFromIso8601String(value));
}
else if(field.equals(NOTIFICATION_FLAGS)) {
setNotificationFlags(Integer.parseInt(value));
}
else if(field.equals(LAST_NOTIFIED)) {
setLastNotificationTime(DateUtilities.getDateFromIso8601String(value));
}
else if(field.equals(REPEAT_INTERVAL)) {
setRepeatInterval(RepeatInterval.values()[Integer.parseInt(value)]);
}
else if(field.equals(REPEAT_VALUE)) {
setRepeatValue(Integer.parseInt(value));
}
else if(field.equals(FLAGS)) {
setFlags(Integer.parseInt(value));
}
else {
success = false;
}
return success;
}
public void setRepeatInterval(RepeatInterval repeatInterval) {
this.repeatInterval = repeatInterval;
if (repeatValue != null) {
setRepeat(new RepeatInfo(repeatInterval, repeatValue));
}
}
public void setRepeatValue(Integer repeatValue) {
this.repeatValue = repeatValue;
if (repeatInterval != null) {
setRepeat(new RepeatInfo(repeatInterval, repeatValue));
}
}
}

@ -0,0 +1,91 @@
package com.timsu.astrid.utilities;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import java.io.File;
import java.io.FilenameFilter;
public class BackupService extends Service {
/* Inspired heavily by SynchronizationService
*/
private static final long BACKUP_OFFSET = 5*60*1000L;
private static final String BACKUP_ACTION = "backup";
private static final String BACKUP_FILE_NAME_REGEX = "auto\\.\\d{6}\\-\\d{4}\\.xml";
private static final int DAYS_TO_KEEP_BACKUP = 7;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onStart(Intent intent, int startId) {
if (intent.getAction().equals(BACKUP_ACTION)) {
startBackup(this);
}
}
private void startBackup(Context ctx) {
if (ctx == null || ctx.getResources() == null) {
return;
}
if (!Preferences.isBackupEnabled(ctx)) {
return;
}
deleteOldBackups();
TasksXmlExporter exporter = new TasksXmlExporter(true);
exporter.setContext(ctx);
exporter.exportTasks();
}
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);
am.cancel(pendingIntent);
if (!Preferences.isBackupEnabled(ctx)) {
return;
}
am.setInexactRepeating(AlarmManager.RTC, System.currentTimeMillis() + BACKUP_OFFSET,
AlarmManager.INTERVAL_DAY, 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);
am.cancel(pendingIntent);
}
private static Intent createAlarmIntent(Context ctx) {
Intent intent = new Intent(ctx, BackupService.class);
intent.setAction(BACKUP_ACTION);
return intent;
}
private void deleteOldBackups() {
FilenameFilter filter = new FilenameFilter() {
@Override
public boolean accept(File file, String s) {
if (s.matches(BACKUP_FILE_NAME_REGEX)) {
String dateString = s.substring(12, 18);
return DateUtilities.wasCreatedBefore(dateString, DAYS_TO_KEEP_BACKUP);
}
return false;
}
};
File astridDir = TasksXmlExporter.getExportDirectory();
String[] files = astridDir.list(filter);
for (String file : files) {
new File(astridDir, file).delete();
}
}
}

@ -19,16 +19,20 @@
*/
package com.timsu.astrid.utilities;
import java.text.SimpleDateFormat;
import java.util.Date;
import android.content.res.Resources;
import android.util.Log;
import com.timsu.astrid.R;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
public class DateUtilities {
private static SimpleDateFormat format = null;
private static final String ISO_8601_FORMAT = "yyyy-MM-dd'T'HH:mm:ssz";
/** Format a time into a medium length absolute format */
public static String getFormattedDate(Resources r, Date date) {
@ -190,4 +194,51 @@ public class DateUtilities {
return result.toString();
}
/* Format a Date into ISO 8601 Compliant format.
*/
public static String getIso8601String(Date d) {
SimpleDateFormat sdf = new SimpleDateFormat(ISO_8601_FORMAT);
String result = "";
if (d != null) {
result = sdf.format(d);
}
return result;
}
/* Take an ISO 8601 string and return a Date object.
On failure, returns null.
*/
public static Date getDateFromIso8601String(String s) {
SimpleDateFormat df = new SimpleDateFormat(ISO_8601_FORMAT);
try {
return df.parse(s);
} catch (ParseException e) {
Log.e("DateUtilities", "Error parsing ISO 8601 date");
return null;
}
}
/* Get current date and time as a string.
Used in TasksXmlExporter
*/
public static String getDateForExport() {
DateFormat df = new SimpleDateFormat("yyMMdd-HHmm");
return df.format(new Date());
}
public static boolean wasCreatedBefore(String s, int daysAgo) {
DateFormat df = new SimpleDateFormat("yyMMdd");
Date date;
try {
date = df.parse(s);
} catch (ParseException e) {
return false;
}
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, -daysAgo);
Date calDate = cal.getTime();
return date.before(calDate);
}
}

@ -4,11 +4,13 @@ import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import com.timsu.astrid.R;
import com.timsu.astrid.widget.FilePickerBuilder;
import com.timsu.astrid.widget.NNumberPickerDialog;
import com.timsu.astrid.widget.NNumberPickerDialog.OnNNumberPickedListener;
import java.io.File;
public class DialogUtilities {
/**
@ -87,4 +89,12 @@ public class DialogUtilities {
new int[] {0, 0}, new int[] {1, 5}, new int[] {0, 0},
new int[] {99, 59}, new String[] {":", null}).show();
}
/** Display a dialog box with a list of files to pick.
*
*/
public static void filePicker(Context context, String title, File path,
FilePickerBuilder.OnFilePickedListener listener) {
new FilePickerBuilder(context, title, path, listener).show();
}
}

@ -1,17 +1,16 @@
package com.timsu.astrid.utilities;
import java.text.SimpleDateFormat;
import java.util.Date;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.res.Resources;
import android.net.Uri;
import android.preference.PreferenceManager;
import com.timsu.astrid.R;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Preferences {
// pref keys
@ -60,6 +59,9 @@ public class Preferences {
if(!prefs.contains(r.getString(R.string.p_notif_vibrate))) {
editor.putBoolean(r.getString(R.string.p_notif_vibrate), true);
}
if (!prefs.contains(r.getString(R.string.p_backup))) {
editor.putBoolean(r.getString(R.string.p_backup), true);
}
setVisibilityPreferences(prefs, editor, r);
@ -278,6 +280,12 @@ public class Preferences {
editor.commit();
}
// --- backup preferences
public static boolean isBackupEnabled(Context context) {
Resources r = context.getResources();
return getPrefs(context).getBoolean(r.getString(R.string.p_backup), true);
}
// --- synchronization preferences
/** RTM authentication token, or null if doesn't exist */

@ -1,7 +1,5 @@
package com.timsu.astrid.utilities;
import java.util.List;
import android.Manifest;
import android.app.AlarmManager;
import android.app.AlertDialog;
@ -9,17 +7,18 @@ import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.util.Log;
import com.timsu.astrid.R;
import com.timsu.astrid.activities.SyncPreferences;
import com.timsu.astrid.appwidget.AstridAppWidgetProvider.UpdateService;
import com.timsu.astrid.sync.SynchronizationService;
import java.util.List;
public class StartupReceiver extends BroadcastReceiver {
private static boolean hasStartedUp = false;
@ -85,6 +84,9 @@ public class StartupReceiver extends BroadcastReceiver {
// start synchronization service
SynchronizationService.scheduleService(context);
// start backup service
BackupService.scheduleService(context);
}
}).start();

@ -0,0 +1,268 @@
package com.timsu.astrid.utilities;
import android.content.Context;
import android.database.Cursor;
import android.os.Environment;
import android.os.Looper;
import android.util.Log;
import android.util.Xml;
import android.widget.Toast;
import com.timsu.astrid.R;
import com.timsu.astrid.data.alerts.AlertController;
import com.timsu.astrid.data.sync.SyncDataController;
import com.timsu.astrid.data.sync.SyncMapping;
import com.timsu.astrid.data.tag.TagController;
import com.timsu.astrid.data.tag.TagIdentifier;
import com.timsu.astrid.data.tag.TagModelForView;
import com.timsu.astrid.data.task.TaskController;
import com.timsu.astrid.data.task.TaskIdentifier;
import com.timsu.astrid.data.task.TaskModelForXml;
import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;
public class TasksXmlExporter {
private TaskController taskController;
private TagController tagController;
private AlertController alertController;
private SyncDataController syncDataController;
private Context ctx;
private String output;
private boolean isService;
private int exportCount;
private XmlSerializer xml;
private HashMap<TagIdentifier, TagModelForView> tagMap;
public static final String ASTRID_TAG = "astrid";
public static final String ASTRID_ATTR_VERSION = "version";
public static final String TASK_TAG = "task";
public static final String TAG_TAG = "tag";
public static final String TAG_ATTR_NAME = "name";
public static final String ALERT_TAG = "alert";
public static final String ALERT_ATTR_DATE = "date";
public static final String SYNC_TAG = "sync";
public static final String SYNC_ATTR_SERVICE = "service";
public static final String SYNC_ATTR_REMOTE_ID = "remote_id";
public static final String XML_ENCODING = "utf-8";
public static final String ASTRID_DIR = "/astrid";
private static final String EXPORT_FILE_NAME = "user.%s.xml";
private static final String BACKUP_FILE_NAME = "auto.%s.xml";
public TasksXmlExporter(boolean isService) {
this.isService = isService;
this.exportCount = 0;
}
private void initTagMap() {
tagMap = tagController.getAllTagsAsMap();
}
private void serializeTags(TaskIdentifier task)
throws IOException {
LinkedList<TagIdentifier> tags = tagController.getTaskTags(task);
for (TagIdentifier tag : tags) {
xml.startTag(null, TAG_TAG);
xml.attribute(null, TAG_ATTR_NAME, tagMap.get(tag).toString());
xml.endTag(null, TAG_TAG);
}
}
private void serializeSyncMappings(TaskIdentifier task)
throws IOException {
HashSet<SyncMapping> syncMappings = syncDataController.getSyncMappings(task);
for (SyncMapping sync : syncMappings) {
xml.startTag(null, SYNC_TAG);
xml.attribute(null, SYNC_ATTR_SERVICE,
Integer.toString(sync.getSyncServiceId()));
xml.attribute(null, SYNC_ATTR_REMOTE_ID, sync.getRemoteId());
xml.endTag(null, SYNC_TAG);
}
}
private void serializeAlerts(TaskIdentifier task)
throws IOException {
List<Date> alerts = alertController.getTaskAlerts(task);
for (Date alert : alerts) {
xml.startTag(null, ALERT_TAG);
xml.attribute(null, ALERT_ATTR_DATE, DateUtilities.getIso8601String(alert));
xml.endTag(null, ALERT_TAG);
}
}
private void serializeTasks()
throws IOException {
Cursor c = taskController.getBackupTaskListCursor();
if (! c.moveToFirst()) {
return; // No tasks.
}
do {
TaskModelForXml task = new TaskModelForXml(c);
TaskIdentifier taskId = task.getTaskIdentifier();
xml.startTag(null, TASK_TAG);
HashMap<String, String> taskAttributes = task.getTaskAttributes();
for (String key : taskAttributes.keySet()) {
String value = taskAttributes.get(key);
xml.attribute(null, key, value);
}
serializeTags(taskId);
serializeAlerts(taskId);
serializeSyncMappings(taskId);
xml.endTag(null, TASK_TAG);
this.exportCount++;
} while (c.moveToNext());
c.close();
}
private void doTasksExport() throws IOException {
File xmlFile = new File(this.output);
xmlFile.createNewFile();
FileOutputStream fos = new FileOutputStream(xmlFile);
xml = Xml.newSerializer();
xml.setOutput(fos, XML_ENCODING);
xml.startDocument(null, null);
xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
xml.startTag(null, ASTRID_TAG);
xml.attribute(null, ASTRID_ATTR_VERSION,
Integer.toString(Preferences.getCurrentVersion(ctx)));
openControllers();
initTagMap();
serializeTasks();
closeControllers();
xml.endTag(null, ASTRID_TAG);
xml.endDocument();
xml.flush();
fos.close();
if (!isService) {
displayToast();
}
}
private void displayToast() {
CharSequence text = String.format(ctx.getString(R.string.export_toast),
ctx.getResources().getQuantityString(R.plurals.Ntasks, exportCount,
exportCount), output);
Toast.makeText(ctx, text, Toast.LENGTH_LONG).show();
}
private void displayErrorToast(String error) {
Toast.makeText(ctx, error, Toast.LENGTH_LONG).show();
}
private void closeControllers() {
tagController.close();
taskController.close();
alertController.close();
syncDataController.close();
}
private void openControllers() {
taskController.open();
tagController.open();
alertController.open();
syncDataController.open();
}
public void exportTasks() {
if (isService && !Preferences.isBackupEnabled(ctx)) {
// Automatic backups are disabled.
return;
}
if (setupFile()) {
Thread thread = new Thread(doBackgroundExport);
thread.start();
}
}
public static File getExportDirectory() {
String storageState = Environment.getExternalStorageState();
if (storageState.equals(Environment.MEDIA_MOUNTED)) {
String path = Environment.getExternalStorageDirectory().getAbsolutePath();
path = path + ASTRID_DIR;
return new File(path);
}
return null;
}
private boolean setupFile() {
File astridDir = getExportDirectory();
if (astridDir != null) {
// Check for /sdcard/astrid directory. If it doesn't exist, make it.
if (astridDir.exists() || astridDir.mkdir()) {
String fileName;
if (isService) {
fileName = BACKUP_FILE_NAME;
} else {
fileName = EXPORT_FILE_NAME;
}
fileName = String.format(fileName, DateUtilities.getDateForExport());
setOutput(astridDir.getAbsolutePath() + "/" + fileName);
return true;
} else {
// Unable to make the /sdcard/astrid directory.
String error = ctx.getString(R.string.error_sdcard) + astridDir.getAbsolutePath();
Log.e("TasksXmlExporter", error);
if (!isService) {
displayErrorToast(error);
}
return false;
}
} else {
// Unable to access the sdcard because it's not in the mounted state.
String error = ctx.getString(R.string.error_sdcard);
Log.e("TasksXmlExporter", error);
if (!isService) {
displayErrorToast(error);
}
return false;
}
}
private void setOutput(String file) {
this.output = file;
}
private Runnable doBackgroundExport = new Runnable() {
public void run() {
Looper.prepare();
try {
doTasksExport();
} catch (IOException e) {
Log.e("TasksXmlExporter", "IOException in doTasksExport " + e.getMessage());
}
Looper.loop();
}
};
public void setTaskController(TaskController taskController) {
this.taskController = taskController;
}
public void setTagController(TagController tagController) {
this.tagController = tagController;
}
public void setAlertController(AlertController alertController) {
this.alertController = alertController;
}
public void setSyncDataController(SyncDataController syncDataController) {
this.syncDataController = syncDataController;
}
public void setContext(Context ctx) {
this.ctx = ctx;
setTaskController(new TaskController(ctx));
setTagController(new TagController(ctx));
setAlertController(new AlertController(ctx));
setSyncDataController(new SyncDataController(ctx));
}
}

@ -0,0 +1,298 @@
package com.timsu.astrid.utilities;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.timsu.astrid.R;
import com.timsu.astrid.data.AbstractController;
import com.timsu.astrid.data.alerts.AlertController;
import com.timsu.astrid.data.sync.SyncDataController;
import com.timsu.astrid.data.sync.SyncMapping;
import com.timsu.astrid.data.tag.TagController;
import com.timsu.astrid.data.tag.TagIdentifier;
import com.timsu.astrid.data.tag.TagModelForView;
import com.timsu.astrid.data.task.TaskController;
import com.timsu.astrid.data.task.TaskIdentifier;
import com.timsu.astrid.data.task.TaskModelForXml;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.Date;
public class TasksXmlImporter {
public static final String TAG = "TasksXmlImporter";
public static final String ASTRID_TAG = TasksXmlExporter.ASTRID_TAG;
public static final String ASTRID_ATTR_VERSION = TasksXmlExporter.ASTRID_ATTR_VERSION;
public static final String TASK_TAG = TasksXmlExporter.TASK_TAG;
public static final String TAG_TAG = TasksXmlExporter.TAG_TAG;
public static final String ALERT_TAG = TasksXmlExporter.ALERT_TAG;
public static final String SYNC_TAG = TasksXmlExporter.SYNC_TAG;
public static final String TASK_ID = AbstractController.KEY_ROWID;
public static final String TASK_NAME = TaskModelForXml.NAME;
public static final String TASK_CREATION_DATE = TaskModelForXml.CREATION_DATE;
public static final String TAG_ATTR_NAME = TasksXmlExporter.TAG_ATTR_NAME;
public static final String ALERT_ATTR_DATE = TasksXmlExporter.ALERT_ATTR_DATE;
public static final String SYNC_ATTR_SERVICE = TasksXmlExporter.SYNC_ATTR_SERVICE;
public static final String SYNC_ATTR_REMOTE_ID = TasksXmlExporter.SYNC_ATTR_REMOTE_ID;
private TaskController taskController;
private TagController tagController;
private AlertController alertController;
private SyncDataController syncDataController;
private XmlPullParser xpp;
private String input;
private Handler importHandler;
private final Context context;
private int taskCount;
private int importCount;
private int skipCount;
static ProgressDialog progressDialog;
public TasksXmlImporter(Context context) {
this.context = context;
setContext(context);
}
private void setProgressMessage(final String message) {
importHandler.post(new Runnable() {
public void run() {
progressDialog.setMessage(message);
}
});
}
public void importTasks(final Runnable runAfterImport) {
importHandler = new Handler();
importHandler.post(new Runnable() {
@Override
public void run() {
TasksXmlImporter.progressDialog = new ProgressDialog(context);
progressDialog.setIcon(android.R.drawable.ic_dialog_info);
progressDialog.setTitle(R.string.import_progress_title);
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progressDialog.setMessage(context.getString(R.string.import_progress_open));
progressDialog.setCancelable(false);
progressDialog.setIndeterminate(true);
progressDialog.show();
}
});
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
try {
performImport();
if (runAfterImport != null) {
importHandler.post(runAfterImport);
}
} catch (FileNotFoundException e) {
Log.e("TasksXmlImporter", e.getMessage());
} catch (XmlPullParserException e) {
Log.e("TasksXmlImporter", e.getMessage());
}
Looper.loop();
}
}).start();
}
private void performImport() throws FileNotFoundException, XmlPullParserException {
taskCount = 0;
importCount = 0;
skipCount = 0;
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
xpp = factory.newPullParser();
xpp.setInput(new FileReader(input));
setProgressMessage(context.getString(R.string.import_progress_opened));
openControllers();
try {
TaskModelForXml currentTask = null;
while (xpp.next() != XmlPullParser.END_DOCUMENT) {
String tag = xpp.getName();
if (xpp.getEventType() == XmlPullParser.END_TAG) {
// Ignore end tag.
continue;
}
if (tag != null) {
if (tag.equals(ASTRID_TAG)) {
// Process <astrid ... >
// Perform version compatibility check?
}
else if (tag.equals(TASK_TAG)) {
// Parse <task ... >
currentTask = parseTask();
} else if (currentTask != null) {
// These tags all require that we have a task to associate them with.
if (tag.equals(TAG_TAG)) {
// Process <tag ... >
parseTag(currentTask.getTaskIdentifier());
} else if (tag.equals(ALERT_TAG)) {
// Process <alert ... >
parseAlert(currentTask.getTaskIdentifier());
} else if (tag.equals(SYNC_TAG)) {
// Process <sync ... >
parseSync(currentTask.getTaskIdentifier());
}
}
}
}
} catch (Exception e) {
Log.e(TAG, "import error " + e.getMessage());
} finally {
closeControllers();
progressDialog.dismiss();
showSummary();
}
}
private boolean parseSync(TaskIdentifier taskId) {
String service = xpp.getAttributeValue(null, SYNC_ATTR_SERVICE);
String remoteId = xpp.getAttributeValue(null, SYNC_ATTR_REMOTE_ID);
if (service != null && remoteId != null) {
int serviceInt = Integer.parseInt(service);
SyncMapping sm = new SyncMapping(taskId, serviceInt, remoteId);
syncDataController.saveSyncMapping(sm);
return true;
}
return false;
}
private boolean parseAlert(TaskIdentifier taskId) {
String alert = xpp.getAttributeValue(null, ALERT_ATTR_DATE);
if (alert != null) {
Date alertDate = DateUtilities.getDateFromIso8601String(alert);
if (alertDate != null) {
if (! alertController.addAlert(taskId, alertDate)) {
return false;
}
} else {
return false;
}
} else {
return false;
}
return true;
}
private boolean parseTag(TaskIdentifier taskId) {
String tagName = xpp.getAttributeValue(null, TAG_ATTR_NAME);
if (tagName != null) {
TagIdentifier tagId;
TagModelForView tagModel;
tagModel = tagController.fetchTagFromName(tagName);
if (tagModel == null) {
// Tag not found, create a new one.
tagId = tagController.createTag(tagName);
} else {
tagId = tagModel.getTagIdentifier();
}
if (! tagController.addTag(taskId, tagId)) {
return false;
}
} else {
return false;
}
return true;
}
private TaskModelForXml parseTask() {
taskCount++;
setProgressMessage(context.getString(R.string.import_progress_read, taskCount));
TaskModelForXml task = null;
String taskName = xpp.getAttributeValue(null, TASK_NAME);
Date creationDate = null;
String createdString = xpp.getAttributeValue(null, TASK_CREATION_DATE);
if (createdString != null) {
creationDate = DateUtilities.getDateFromIso8601String(createdString);
}
// If the task's name and creation date match an existing task, skip it.
if (creationDate != null && taskName != null) {
task = taskController.fetchTaskForXml(taskName, creationDate);
}
if (task != null) {
// Skip this task.
skipCount++;
setProgressMessage(context.getString(R.string.import_progress_skip, taskCount));
// Set currentTask to null so we skip its alerts/syncs/tags, too.
return null;
}
// Else, make a new task model and add away.
task = new TaskModelForXml();
int numAttributes = xpp.getAttributeCount();
for (int i = 0; i < numAttributes; i++) {
String fieldName = xpp.getAttributeName(i);
String fieldValue = xpp.getAttributeValue(i);
task.setField(fieldName, fieldValue);
}
// Save the task to the database.
taskController.saveTask(task, false);
importCount++;
setProgressMessage(context.getString(R.string.import_progress_add, taskCount));
return task;
}
private void showSummary() {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.import_summary_title);
String message = context.getString(R.string.import_summary_message,
input, taskCount, importCount, skipCount);
builder.setMessage(message);
builder.setPositiveButton(context.getString(android.R.string.ok),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
});
builder.show();
}
public void setTaskController(TaskController taskController) {
this.taskController = taskController;
}
public void setTagController(TagController tagController) {
this.tagController = tagController;
}
public void setAlertController(AlertController alertController) {
this.alertController = alertController;
}
public void setSyncDataController(SyncDataController syncDataController) {
this.syncDataController = syncDataController;
}
public void setContext(Context ctx) {
setTaskController(new TaskController(ctx));
setTagController(new TagController(ctx));
setAlertController(new AlertController(ctx));
setSyncDataController(new SyncDataController(ctx));
}
private void closeControllers() {
taskController.close();
tagController.close();
alertController.close();
syncDataController.close();
}
private void openControllers() {
taskController.open();
tagController.open();
alertController.open();
syncDataController.open();
}
public void setInput(String input) {
this.input = input;
}
}

@ -0,0 +1,58 @@
package com.timsu.astrid.widget;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import java.io.File;
import java.io.FilenameFilter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class FilePickerBuilder extends AlertDialog.Builder implements DialogInterface.OnClickListener {
public interface OnFilePickedListener {
void onFilePicked(String filePath);
}
private OnFilePickedListener callback;
private String[] files;
private String path;
private FilenameFilter filter;
public FilePickerBuilder(Context ctx, String title, File path, OnFilePickedListener callback) {
super(ctx);
filter = new FilenameFilter() {
@Override
public boolean accept(File dir, String s) {
File file = new File(dir, s);
return file.isFile();
}
};
setTitle(title);
setPath(path);
this.callback = callback;
}
public void setFilter(FilenameFilter filter) {
this.filter = filter;
}
private void setPath(File path) {
this.path = path.getAbsolutePath();
// Reverse the order of the file list so newest timestamped file is first.
List<String> fileList = Arrays.asList(path.list(filter));
Collections.sort(fileList);
Collections.reverse(fileList);
files = (String[])fileList.toArray();
setItems(files, this);
}
@Override
public void onClick(DialogInterface dialogInterface, int i) {
if (callback != null) {
callback.onFilePicked(path + "/" + files[i]);
}
}
}
Loading…
Cancel
Save