diff --git a/astrid/AndroidManifest.xml b/astrid/AndroidManifest.xml index c9673f522..effd0aae9 100644 --- a/astrid/AndroidManifest.xml +++ b/astrid/AndroidManifest.xml @@ -376,6 +376,12 @@ + + + + + + @@ -410,6 +416,12 @@ + + + + + + diff --git a/astrid/api-src/com/todoroo/astrid/api/AstridApiConstants.java b/astrid/api-src/com/todoroo/astrid/api/AstridApiConstants.java index fac205bcc..8292604e6 100644 --- a/astrid/api-src/com/todoroo/astrid/api/AstridApiConstants.java +++ b/astrid/api-src/com/todoroo/astrid/api/AstridApiConstants.java @@ -133,17 +133,16 @@ public class AstridApiConstants { /** * Action name for broadcast intent requesting a listing of active - * sync providers users can activate from the menu + * sync actions users can activate from the menu */ - public static final String BROADCAST_REQUEST_SYNC_PROVDERS = PACKAGE + ".REQUEST_SYNC_PROVIDERS"; + public static final String BROADCAST_REQUEST_SYNC_ACTIONS = PACKAGE + ".REQUEST_SYNC_ACTIONS"; /** * Action name for broadcast intent sending sync provider information back to Astrid * @extra EXTRAS_ADDON your add-on identifier - * @extra EXTRAS_NAME label for your sync provider - * @extra EXTRAS_RESPONSE a PendingIntent to invoke synchronization + * @extra EXTRAS_RESPONSE a {@link SyncAction} to invoke synchronization */ - public static final String BROADCAST_SEND_SYNC_PROVIDER = PACKAGE + ".SEND_SYNC_PROVIDER"; + public static final String BROADCAST_SEND_SYNC_ACTIONS = PACKAGE + ".SEND_SYNC_ACTIONS"; // --- Task Actions API diff --git a/astrid/api-src/com/todoroo/astrid/api/SyncAction.java b/astrid/api-src/com/todoroo/astrid/api/SyncAction.java new file mode 100644 index 000000000..207bfc3fe --- /dev/null +++ b/astrid/api-src/com/todoroo/astrid/api/SyncAction.java @@ -0,0 +1,87 @@ +/** + * See the file "LICENSE" for the full license governing this code. + */ +package com.todoroo.astrid.api; + +import android.app.PendingIntent; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Represents an intent that can be called to perform synchronization + * + * @author Tim Su + * + */ +public class SyncAction implements Parcelable { + + /** + * Label + */ + public String label = null; + + /** + * Intent to call when invoking this operation + */ + public PendingIntent intent = null; + + /** + * Create an EditOperation object + * + * @param label + * label to display + * @param intent + * intent to invoke. {@link EXTRAS_TASK_ID} will be passed + */ + public SyncAction(String label, PendingIntent intent) { + super(); + this.label = label; + this.intent = intent; + } + + /** + * Returns the label of this action + */ + @Override + public String toString() { + return label; + } + + // --- parcelable helpers + + /** + * {@inheritDoc} + */ + public int describeContents() { + return 0; + } + + /** + * {@inheritDoc} + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(label); + dest.writeParcelable(intent, 0); + } + + /** + * Parcelable creator + */ + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + /** + * {@inheritDoc} + */ + public SyncAction createFromParcel(Parcel source) { + return new SyncAction(source.readString(), (PendingIntent)source.readParcelable( + PendingIntent.class.getClassLoader())); + } + + /** + * {@inheritDoc} + */ + public SyncAction[] newArray(int size) { + return new SyncAction[size]; + }; + }; + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevSyncActionExposer.java b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevSyncActionExposer.java new file mode 100644 index 000000000..de3b75500 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevSyncActionExposer.java @@ -0,0 +1,39 @@ +/** + * See the file "LICENSE" for the full license governing this code. + */ +package com.todoroo.astrid.producteev; + +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.timsu.astrid.R; +import com.todoroo.astrid.api.AstridApiConstants; +import com.todoroo.astrid.api.SyncAction; + +/** + * Exposes sync action + * + */ +public class ProducteevSyncActionExposer extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + // if we aren't logged in, don't expose sync action + if(!ProducteevUtilities.INSTANCE.isLoggedIn()) + return; + + Intent syncIntent = new Intent(ProducteevBackgroundService.SYNC_ACTION, null, + context, ProducteevBackgroundService.class); + PendingIntent pendingIntent = PendingIntent.getService(context, 0, syncIntent, PendingIntent.FLAG_UPDATE_CURRENT); + SyncAction syncAction = new SyncAction(context.getString(R.string.producteev_PPr_header), + pendingIntent); + + Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_SYNC_ACTIONS); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, ProducteevUtilities.IDENTIFIER); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, syncAction); + context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/rmilk/MilkSyncActionExposer.java b/astrid/plugin-src/com/todoroo/astrid/rmilk/MilkSyncActionExposer.java new file mode 100644 index 000000000..396709dc7 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/rmilk/MilkSyncActionExposer.java @@ -0,0 +1,39 @@ +/** + * See the file "LICENSE" for the full license governing this code. + */ +package com.todoroo.astrid.rmilk; + +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.timsu.astrid.R; +import com.todoroo.astrid.api.AstridApiConstants; +import com.todoroo.astrid.api.SyncAction; + +/** + * Exposes sync action + * + */ +public class MilkSyncActionExposer extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + // if we aren't logged in, don't expose sync action + if(!MilkUtilities.isLoggedIn()) + return; + + Intent syncIntent = new Intent(MilkBackgroundService.SYNC_ACTION, null, + context, MilkBackgroundService.class); + PendingIntent pendingIntent = PendingIntent.getService(context, 0, syncIntent, PendingIntent.FLAG_UPDATE_CURRENT); + SyncAction syncAction = new SyncAction(context.getString(R.string.rmilk_MPr_header), + pendingIntent); + + Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_SYNC_ACTIONS); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, MilkUtilities.IDENTIFIER); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, syncAction); + context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/rmilk/data/MilkNote.java b/astrid/plugin-src/com/todoroo/astrid/rmilk/data/MilkNote.java index 3803d9952..aabb2f6ad 100644 --- a/astrid/plugin-src/com/todoroo/astrid/rmilk/data/MilkNote.java +++ b/astrid/plugin-src/com/todoroo/astrid/rmilk/data/MilkNote.java @@ -46,7 +46,7 @@ public class MilkNote { /** * Turn a note's title and text into a string * @param title - * @param text + * @param label * @return */ @SuppressWarnings("nls") diff --git a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java index ebec6b994..c2b4d123d 100644 --- a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java +++ b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java @@ -1,6 +1,7 @@ package com.todoroo.astrid.activity; import java.util.Date; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map.Entry; import java.util.Timer; @@ -9,6 +10,7 @@ import java.util.concurrent.atomic.AtomicReference; import android.app.AlertDialog; import android.app.ListActivity; +import android.app.PendingIntent.CanceledException; import android.content.BroadcastReceiver; import android.content.ContentValues; import android.content.Context; @@ -38,6 +40,7 @@ import android.view.inputmethod.EditorInfo; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.ImageButton; import android.widget.ImageView; @@ -65,6 +68,7 @@ import com.todoroo.astrid.adapter.TaskAdapter.ViewHolder; import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.api.PermaSql; +import com.todoroo.astrid.api.SyncAction; import com.todoroo.astrid.api.TaskAction; import com.todoroo.astrid.api.TaskDecoration; import com.todoroo.astrid.backup.BackupActivity; @@ -110,17 +114,17 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, private static final int MENU_ADDONS_ID = Menu.FIRST + 1; private static final int MENU_SETTINGS_ID = Menu.FIRST + 2; private static final int MENU_SORT_ID = Menu.FIRST + 3; - private static final int MENU_HELP_ID = Menu.FIRST + 4; - private static final int MENU_ADDON_INTENT_ID = Menu.FIRST + 5; + private static final int MENU_SYNC_ID = Menu.FIRST + 4; + private static final int MENU_HELP_ID = Menu.FIRST + 5; + private static final int MENU_ADDON_INTENT_ID = Menu.FIRST + 6; - private static final int CONTEXT_MENU_EDIT_TASK_ID = Menu.FIRST + 6; - private static final int CONTEXT_MENU_DELETE_TASK_ID = Menu.FIRST + 7; - private static final int CONTEXT_MENU_UNDELETE_TASK_ID = Menu.FIRST + 8; - private static final int CONTEXT_MENU_PURGE_TASK_ID = Menu.FIRST + 9; - private static final int CONTEXT_MENU_ADDON_INTENT_ID = Menu.FIRST + 10; + private static final int CONTEXT_MENU_EDIT_TASK_ID = Menu.FIRST + 20; + private static final int CONTEXT_MENU_DELETE_TASK_ID = Menu.FIRST + 21; + private static final int CONTEXT_MENU_UNDELETE_TASK_ID = Menu.FIRST + 22; + private static final int CONTEXT_MENU_PURGE_TASK_ID = Menu.FIRST + 23; + private static final int CONTEXT_MENU_ADDON_INTENT_ID = Menu.FIRST + 24; - /** menu code indicating the end of the context menu */ - private static final int CONTEXT_MENU_DEBUG = Menu.FIRST + 11; + private static final int CONTEXT_MENU_DEBUG = Menu.FIRST + 30; // --- constants @@ -150,6 +154,7 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, protected TaskAdapter taskAdapter = null; protected DetailReceiver detailReceiver = new DetailReceiver(); protected RefreshReceiver refreshReceiver = new RefreshReceiver(); + protected SyncActionReceiver syncActionReceiver = new SyncActionReceiver(); private ImageButton quickAddButton; private EditText quickAddBox; @@ -158,6 +163,7 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, private int sortSort; private final AtomicReference sqlQueryTemplate = new AtomicReference(); private Timer backgroundTimer; + private final LinkedHashSet syncActions = new LinkedHashSet(); /* ====================================================================== * ======================================================= initialization @@ -240,6 +246,12 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, R.string.TLA_menu_sort); item.setIcon(android.R.drawable.ic_menu_sort_by_size); + if(syncActions.size() > 0) { + item = menu.add(Menu.NONE, MENU_SYNC_ID, Menu.NONE, + R.string.TLA_menu_sync); + item.setIcon(R.drawable.ic_menu_refresh); + } + item = menu.add(Menu.NONE, MENU_HELP_ID, Menu.NONE, R.string.TLA_menu_help); item.setIcon(android.R.drawable.ic_menu_help); @@ -430,6 +442,8 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, new IntentFilter(AstridApiConstants.BROADCAST_SEND_ACTIONS)); registerReceiver(refreshReceiver, new IntentFilter(AstridApiConstants.BROADCAST_EVENT_REFRESH)); + registerReceiver(syncActionReceiver, + new IntentFilter(AstridApiConstants.BROADCAST_SEND_SYNC_ACTIONS)); setUpBackgroundJobs(); } @@ -450,6 +464,9 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, protected class RefreshReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { + if(intent == null || !AstridApiConstants.BROADCAST_EVENT_REFRESH.equals(intent.getAction())) + return; + runOnUiThread(new Runnable() { @Override public void run() { @@ -460,6 +477,29 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, } } + /** + * Receiver which receives sync provider intents + * + * @author Tim Su + * + */ + protected class SyncActionReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if(intent == null || !AstridApiConstants.BROADCAST_SEND_SYNC_ACTIONS.equals(intent.getAction())) + return; + + try { + Bundle extras = intent.getExtras(); + SyncAction syncAction = extras.getParcelable(AstridApiConstants.EXTRAS_RESPONSE); + syncActions.add(syncAction); + } catch (Exception e) { + exceptionService.reportError("receive-sync-action-" + //$NON-NLS-1$ + intent.getStringExtra(AstridApiConstants.EXTRAS_ADDON), e); + } + } + } + /** * Receiver which receives detail or decoration intents * @@ -563,6 +603,11 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, if(oldListItemSelected != ListView.INVALID_POSITION && oldListItemSelected < taskCursor.getCount()) getListView().setSelection(oldListItemSelected); + + // also load sync actions + syncActions.clear(); + Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_REQUEST_SYNC_ACTIONS); + sendOrderedBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); } /** @@ -785,6 +830,44 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, .show(); } + private void performSyncAction() { + if(syncActions.size() == 0) + return; + if(syncActions.size() == 1) { + SyncAction syncAction = syncActions.iterator().next(); + try { + syncAction.intent.send(); + Toast.makeText(this, R.string.SyP_progress_toast, + Toast.LENGTH_LONG).show(); + } catch (CanceledException e) { + // + } + } else { + final SyncAction[] actions = syncActions.toArray(new SyncAction[syncActions.size()]); + ArrayAdapter adapter = new ArrayAdapter(this, + android.R.layout.simple_spinner_dropdown_item, actions); + DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface click, int which) { + try { + actions[which].intent.send(); + Toast.makeText(TaskListActivity.this, R.string.SyP_progress_toast, + Toast.LENGTH_LONG).show(); + } catch (CanceledException e) { + // + } + } + }; + + // show a menu of available options + new AlertDialog.Builder(this) + .setTitle(R.string.SyP_label) + .setAdapter(adapter, listener) + .show().setOwnerActivity(this); + + } + } + @Override public boolean onMenuItemSelected(int featureId, final MenuItem item) { Intent intent; @@ -805,6 +888,9 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, this, sortFlags, sortSort); dialog.show(); return true; + case MENU_SYNC_ID: + performSyncAction(); + return true; case MENU_HELP_ID: intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://weloveastrid.com/help-user-guide-astrid-v3/active-tasks/")); //$NON-NLS-1$