diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 7f3d61b9d..b278cbb36 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,16 +1,16 @@ + android:versionCode="56" + android:versionName="1.12"> + android:value="56" /> - + @@ -34,6 +34,8 @@ + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 817470e30..db15beb97 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -88,6 +88,7 @@ Tags Display More + Synchronization Settings Help @@ -182,7 +183,7 @@ If you don\'t want to see the new task right after you complete the old one, you Ago % of Task Finished - + Astrid: Tag View: Create Task With Tag @@ -193,12 +194,26 @@ If you don\'t want to see the new task right after you complete the old one, you Sort A-Z Sort by Size - - + + + + + sync_rtm + Synchronization Services + Remember The Milk + http://www.rememberthemilk.com + Sync Error! + +In order to synchronize, please log in to your %s account and authorize Astrid to read your data. +\n\n +When finished, restart Astrid. + + + Information Question - Let's do it! + Let\'s do it! Snooze! No, quit. Hours/minutes to snooze? diff --git a/res/xml/sync_preferences.xml b/res/xml/sync_preferences.xml new file mode 100644 index 000000000..af836e2f4 --- /dev/null +++ b/res/xml/sync_preferences.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/com/timsu/astrid/activities/SyncPreferences.java b/src/com/timsu/astrid/activities/SyncPreferences.java new file mode 100644 index 000000000..501e98544 --- /dev/null +++ b/src/com/timsu/astrid/activities/SyncPreferences.java @@ -0,0 +1,15 @@ +package com.timsu.astrid.activities; + +import android.os.Bundle; +import android.preference.PreferenceActivity; + +import com.timsu.astrid.R; + +public class SyncPreferences extends PreferenceActivity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + addPreferencesFromResource(R.xml.sync_preferences); + } +} \ No newline at end of file diff --git a/src/com/timsu/astrid/activities/TaskList.java b/src/com/timsu/astrid/activities/TaskList.java index 4684e70d1..33eb5ec1f 100644 --- a/src/com/timsu/astrid/activities/TaskList.java +++ b/src/com/timsu/astrid/activities/TaskList.java @@ -57,6 +57,7 @@ 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.TaskModelForList; +import com.timsu.astrid.sync.Synchronizer; import com.timsu.astrid.utilities.Constants; import com.timsu.astrid.utilities.StartupReceiver; @@ -77,6 +78,7 @@ public class TaskList extends Activity { private static final int ACTIVITY_VIEW = 1; private static final int ACTIVITY_EDIT = 2; private static final int ACTIVITY_TAGS = 3; + private static final int ACTIVITY_SYNCHRONIZE = 4; // menu codes private static final int INSERT_ID = Menu.FIRST; @@ -84,8 +86,9 @@ public class TaskList extends Activity { private static final int TAGS_ID = Menu.FIRST + 2; private static final int MORE_ID = Menu.FIRST + 3; - private static final int OPTIONS_SETTINGS_ID = Menu.FIRST + 10; - private static final int OPTIONS_HELP_ID = Menu.FIRST + 11; + private static final int OPTIONS_SYNC_ID = Menu.FIRST + 10; + private static final int OPTIONS_SETTINGS_ID = Menu.FIRST + 11; + private static final int OPTIONS_HELP_ID = Menu.FIRST + 12; private static final int CONTEXT_FILTER_HIDDEN = Menu.FIRST + 20; private static final int CONTEXT_FILTER_DONE = Menu.FIRST + 21; @@ -163,7 +166,7 @@ public class TaskList extends Activity { }); fillData(); - // TODO Synchronizer.authenticate(this); + Synchronizer.synchronize(this); gestureDetector = new GestureDetector(new TaskListGestureDetector()); gestureTouchListener = new View.OnTouchListener() { @@ -238,14 +241,16 @@ public class TaskList extends Activity { public boolean onCreateMoreOptionsMenu(Menu menu) { MenuItem item; + item = menu.add(Menu.NONE, OPTIONS_SYNC_ID, Menu.NONE, + R.string.taskList_menu_sync); + item.setAlphabeticShortcut('s'); + item = menu.add(Menu.NONE, OPTIONS_SETTINGS_ID, Menu.NONE, R.string.taskList_menu_settings); - item.setIcon(android.R.drawable.ic_menu_preferences); item.setAlphabeticShortcut('p'); item = menu.add(Menu.NONE, OPTIONS_HELP_ID, Menu.NONE, R.string.taskList_menu_help); - item.setIcon(android.R.drawable.ic_menu_help); item.setAlphabeticShortcut('h'); return true; @@ -520,9 +525,10 @@ public class TaskList extends Activity { protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - /** Handle case where authentication just happened */ - if(resultCode == Constants.RESULT_SYNCHRONIZE) { - // TODO Synchronizer.performSync(this, true); + /** Handle synchronization callbacks */ + if(requestCode == ACTIVITY_SYNCHRONIZE) { + Synchronizer.synchronizerStatusUpdated(this); + Synchronizer.synchronize(this); } if(requestCode == ACTIVITY_TAGS && resultCode == RESULT_CANCELED) @@ -601,6 +607,10 @@ public class TaskList extends Activity { layout.showContextMenu(); return true; + case OPTIONS_SYNC_ID: + startActivityForResult(new Intent(this, SyncPreferences.class), + ACTIVITY_SYNCHRONIZE); + return true; case OPTIONS_SETTINGS_ID: startActivity(new Intent(this, EditPreferences.class)); return true; diff --git a/src/com/timsu/astrid/sync/RTMSyncService.java b/src/com/timsu/astrid/sync/RTMSyncService.java new file mode 100644 index 000000000..f8f886cf7 --- /dev/null +++ b/src/com/timsu/astrid/sync/RTMSyncService.java @@ -0,0 +1,119 @@ +package com.timsu.astrid.sync; + +import java.util.Map.Entry; + +import android.app.Activity; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Resources; +import android.net.Uri; +import android.util.Log; + +import com.mdt.rtm.ApplicationInfo; +import com.mdt.rtm.ServiceImpl; +import com.mdt.rtm.data.RtmList; +import com.mdt.rtm.data.RtmLists; +import com.mdt.rtm.data.RtmAuth.Perms; +import com.timsu.astrid.R; +import com.timsu.astrid.utilities.DialogUtilities; +import com.timsu.astrid.utilities.Preferences; + +public class RTMSyncService implements SynchronizationService { + + private ServiceImpl rtmService = null; + private int id; + + public RTMSyncService(int id) { + this.id = id; + } + + @Override + public void synchronize(Activity activity) { + authenticate(activity); + } + + @Override + public void synchronizationDisabled(Activity activity) { + Preferences.setSyncRTMToken(activity, null); + } + + /** Perform authentication with RTM. Will open the SyncBrowser if necessary */ + private void authenticate(final Activity activity) { + try { + String apiKey = "bd9883b3384a21ead17501da38bb1e68"; + String sharedSecret = "a19b2a020345219b"; + String appName = null; + String authToken = Preferences.getSyncRTMToken(activity); + + // check if our auth token works + if(authToken != null) { + rtmService = new ServiceImpl(new ApplicationInfo( + apiKey, sharedSecret, appName, authToken)); + if(!rtmService.isServiceAuthorized()) // re-do login + authToken = null; + } + + if(authToken == null) { + // try completing the authorization. + if(rtmService != null) { + try { + String token = rtmService.completeAuthorization(); + Log.w("astrid", "got RTM token: " + token); + Preferences.setSyncRTMToken(activity, token); + performSync(activity); + + return; + } catch (Exception e) { + // didn't work. do the process again. + } + } + + rtmService = new ServiceImpl(new ApplicationInfo( + apiKey, sharedSecret, appName)); + final String url = rtmService.beginAuthorization(Perms.delete); + + Resources r = activity.getResources(); + DialogUtilities.okCancelDialog(activity, + r.getString(R.string.sync_auth_request, "RTM"), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Intent intent = new Intent(Intent.ACTION_VIEW, + Uri.parse(url)); + activity.startActivity(intent); + } + }, null); + } else { + performSync(activity); + } + + } catch (Exception e) { + Synchronizer.showError(activity, e); + } + } + + private void performSync(Activity activity) { + try { + Log.i("astrid", "isAuthorized: " + rtmService.isServiceAuthorized()); + RtmLists lists = rtmService.lists_getList(); + for(Entry list : lists.getLists().entrySet()) { + Log.i("astrid", "look, " + list.getKey()); + } + + // fetch tasks that've changed since last sync + + // grab my own list of tasks that have changed since last sync + + + // if we find a conflict... remember and ignore + + + // update tasks that have changed + + + } catch (Exception e) { + Synchronizer.showError(activity, e); + } + } + +} diff --git a/src/com/timsu/astrid/sync/SynchronizationService.java b/src/com/timsu/astrid/sync/SynchronizationService.java new file mode 100644 index 000000000..caa938bff --- /dev/null +++ b/src/com/timsu/astrid/sync/SynchronizationService.java @@ -0,0 +1,17 @@ +package com.timsu.astrid.sync; + +import android.app.Activity; + +/** A service that synchronizes with Astrid + * + * @author timsu + * + */ +public interface SynchronizationService { + + /** Synchronize with the service */ + void synchronize(Activity activity); + + /** Called when synchronization with this service is turned off */ + void synchronizationDisabled(Activity activity); +} diff --git a/src/com/timsu/astrid/sync/Synchronizer.java b/src/com/timsu/astrid/sync/Synchronizer.java index c64c760ee..c5b87db3e 100644 --- a/src/com/timsu/astrid/sync/Synchronizer.java +++ b/src/com/timsu/astrid/sync/Synchronizer.java @@ -1,71 +1,62 @@ package com.timsu.astrid.sync; -import java.util.Map.Entry; +import java.util.HashMap; +import java.util.Map; import android.app.Activity; -import android.content.Intent; -import android.net.Uri; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.Resources; import android.util.Log; -import com.mdt.rtm.ApplicationInfo; -import com.mdt.rtm.ServiceImpl; -import com.mdt.rtm.data.RtmList; -import com.mdt.rtm.data.RtmLists; -import com.mdt.rtm.data.RtmAuth.Perms; -import com.timsu.astrid.utilities.Constants; +import com.timsu.astrid.R; +import com.timsu.astrid.utilities.DialogUtilities; +import com.timsu.astrid.utilities.Preferences; public class Synchronizer { - private static ServiceImpl rtmService = null; + /* Synchronization Service ID's */ + private static final int SYNC_ID_RTM = 1; - public static void authenticate(Activity activity) { - try { - String apiKey = "127d19adab1a7b6922d8dfda3ef09645"; - String sharedSecret = "503816890a685753"; - String appName = null; - String authToken = null; - - // check if our auth token works - if(authToken != null) { - rtmService = new ServiceImpl(new ApplicationInfo( - apiKey, sharedSecret, appName, authToken)); - if(!rtmService.isServiceAuthorized()) // re-do login - authToken = null; - } + /** Service map */ + private static Map services = + new HashMap(); + static { + services.put(SYNC_ID_RTM, new RTMSyncService(SYNC_ID_RTM)); + } - if(authToken == null) { - rtmService = new ServiceImpl(new ApplicationInfo( - apiKey, sharedSecret, appName)); - String url = rtmService.beginAuthorization(Perms.delete); + // --- public interface - Intent browserIntent = new Intent(Intent.ACTION_VIEW, - Uri.parse(url)); - activity.startActivityForResult(browserIntent, - Constants.RESULT_SYNCHRONIZE); - } else { - performSync(activity, false); - } + /** Synchronize all activated sync services */ + public static void synchronize(Activity activity) { + // RTM sync + if(Preferences.shouldSyncRTM(activity)) { + services.get(SYNC_ID_RTM).synchronize(activity); + } + } - } catch (Exception e) { - Log.e("astrid", "error parsing", e); // TODO dialog box + /** Clears tokens if services are disabled */ + public static void synchronizerStatusUpdated(Activity activity) { + if(!Preferences.shouldSyncRTM(activity)) { + services.get(SYNC_ID_RTM).synchronizationDisabled(activity); } } - public static void performSync(Activity activity, boolean justLoggedIn) { - try { - // store token - if(justLoggedIn) { - String token = rtmService.completeAuthorization(); - Log.w("astrid", "LOOK A TOKEN " + token); - } + // --- package utilities + + /** Utility class for showing synchronization errors */ + static void showError(Context context, Throwable e) { + Log.e("astrid", "Synchronization Error", e); - System.out.println("isAuthorized: " + rtmService.isServiceAuthorized()); - RtmLists lists = rtmService.lists_getList(); - for(Entry list : lists.getLists().entrySet()) { - Log.i("astrid", "look, " + list.getKey()); + Resources r = context.getResources(); + DialogUtilities.okDialog(context, + r.getString(R.string.sync_error) + " " + + e.getLocalizedMessage(), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // do nothing? } - } catch (Exception e) { - Log.e("astrid", "error parsing", e); // TODO dialog box - } + }); } + } diff --git a/src/com/timsu/astrid/utilities/DialogUtilities.java b/src/com/timsu/astrid/utilities/DialogUtilities.java index 3e2034391..192cf878f 100644 --- a/src/com/timsu/astrid/utilities/DialogUtilities.java +++ b/src/com/timsu/astrid/utilities/DialogUtilities.java @@ -17,4 +17,16 @@ public class DialogUtilities { .setPositiveButton(android.R.string.ok, okListener) .show(); } + + public static void okCancelDialog(Context context, String text, + DialogInterface.OnClickListener okListener, + DialogInterface.OnClickListener cancelListener) { + new AlertDialog.Builder(context) + .setTitle(R.string.information_title) + .setMessage(text) + .setIcon(android.R.drawable.ic_dialog_alert) + .setPositiveButton(android.R.string.ok, okListener) + .setNegativeButton(android.R.string.cancel, cancelListener) + .show(); + } } diff --git a/src/com/timsu/astrid/utilities/Preferences.java b/src/com/timsu/astrid/utilities/Preferences.java index 10ee5ae9f..fe67515e1 100644 --- a/src/com/timsu/astrid/utilities/Preferences.java +++ b/src/com/timsu/astrid/utilities/Preferences.java @@ -14,6 +14,7 @@ public class Preferences { // pref keys private static final String P_CURRENT_VERSION = "cv"; private static final String P_SHOW_REPEAT_HELP = "repeathelp"; + private static final String P_SYNC_RTM_TOKEN = "rtmtoken"; // default values private static final boolean DEFAULT_PERSISTENCE_MODE = true; @@ -117,8 +118,35 @@ public class Preferences { return getIntegerValue(context, R.string.p_notif_defaultRemind); } + // --- synchronization preferences + + /** RTM authentication token, or null if doesn't exist */ + public static String getSyncRTMToken(Context context) { + return getPrefs(context).getString(P_SYNC_RTM_TOKEN, null); + } + + /** Sets the RTM authentication token. Set to null to clear. */ + public static void setSyncRTMToken(Context context, String setting) { + Editor editor = getPrefs(context).edit(); + editor.putString(P_SYNC_RTM_TOKEN, setting); + editor.commit(); + } + + /** Should sync with RTM? */ + public static boolean shouldSyncRTM(Context context) { + Resources r = context.getResources(); + return getPrefs(context).getBoolean(r.getString( + R.string.p_sync_rtm), false); + } + // --- helper methods + private static void clearPref(Context context, String key) { + Editor editor = getPrefs(context).edit(); + editor.remove(key); + editor.commit(); + } + private static SharedPreferences getPrefs(Context context) { return PreferenceManager.getDefaultSharedPreferences(context); }