Merge remote branch 'origin/master' into dev

Conflicts:
	astrid/AndroidManifest.xml
	astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevDataService.java
	astrid/plugin-src/com/todoroo/astrid/rmilk/MilkBackgroundService.java
	astrid/plugin-src/com/todoroo/astrid/rmilk/MilkDetailExposer.java
	astrid/plugin-src/com/todoroo/astrid/rmilk/MilkPreferences.java
	astrid/plugin-src/com/todoroo/astrid/rmilk/api/data/RtmTask.java
	astrid/plugin-src/com/todoroo/astrid/rmilk/data/MilkNote.java
	astrid/plugin-src/com/todoroo/astrid/rmilk/sync/RTMSyncProvider.java
	astrid/plugin-src/com/todoroo/astrid/rmilk/sync/RTMTaskContainer.java
	astrid/src/com/todoroo/astrid/activity/TaskListActivity.java
	astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java
	astrid/src/com/todoroo/astrid/service/StartupService.java
pull/14/head
Tim Su 14 years ago
commit 33ae2cad02

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.timsu.astrid"
android:versionName="3.2.5 (build your own filters, easy sorting, customizable widget, ui improvements)"
android:versionCode="152">
android:versionName="3.2.7 (bug fixes and ui improvements)"
android:versionCode="154">
<!-- widgets, alarms, and services will break if Astrid is installed on SD card -->
<!-- android:installLocation="internalOnly"> -->
@ -196,6 +196,12 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<receiver android:name="com.todoroo.astrid.alarms.AlarmDetailExposer">
<intent-filter>
<action android:name="com.todoroo.astrid.REQUEST_DETAILS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<!-- tags -->
<receiver android:name="com.todoroo.astrid.tags.TagsPlugin">
@ -209,6 +215,12 @@
<action android:name="com.todoroo.astrid.REQUEST_FILTERS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<receiver android:name="com.todoroo.astrid.tags.TagDetailExposer">
<intent-filter>
<action android:name="com.todoroo.astrid.REQUEST_DETAILS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<!-- repeats -->
@ -224,6 +236,12 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<receiver android:name="com.todoroo.astrid.repeats.RepeatDetailExposer">
<intent-filter>
<action android:name="com.todoroo.astrid.REQUEST_DETAILS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<!-- calendar -->
<receiver android:name="com.todoroo.astrid.gcal.GCalTaskCompleteListener">
@ -262,6 +280,12 @@
</receiver>
<!-- notes -->
<receiver android:name="com.todoroo.astrid.notes.NoteDetailExposer">
<intent-filter>
<action android:name="com.todoroo.astrid.REQUEST_DETAILS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<!-- locale -->
<activity android:name="com.todoroo.astrid.locale.LocaleEditAlerts"
@ -323,6 +347,7 @@
android:taskAffinity="com.todoroo.astrid.reminders.NotificationActivity"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:finishOnTaskLaunch="true"
android:clearTaskOnLaunch="true" />
<!-- producteev -->
@ -336,7 +361,7 @@
android:icon="@drawable/ic_menu_producteev"
android:label="@string/producteev_PPr_header">
<intent-filter>
<action android:name="com.todoroo.astrid.TASK_LIST_MENU" />
<action android:name="com.todoroo.astrid.SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
@ -344,11 +369,7 @@
</intent-filter>
</activity>
<activity android:name="com.todoroo.astrid.producteev.ProducteevLoginActivity"
android:theme="@style/Theme">
<intent-filter> <!-- temporary -->
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
android:theme="@style/Theme" />
<service android:name="com.todoroo.astrid.producteev.ProducteevBackgroundService"/>
<receiver android:name="com.todoroo.astrid.producteev.ProducteevStartupReceiver">
<intent-filter>
@ -356,6 +377,18 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<receiver android:name="com.todoroo.astrid.producteev.ProducteevDetailExposer">
<intent-filter>
<action android:name="com.todoroo.astrid.REQUEST_DETAILS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<receiver android:name="com.todoroo.astrid.producteev.ProducteevSyncActionExposer">
<intent-filter>
<action android:name="com.todoroo.astrid.REQUEST_SYNC_ACTIONS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
</application>

@ -69,6 +69,11 @@ public class AstridApiConstants {
*/
public static final String EXTRAS_NEW_DUE_DATE = "newDueDate";
/**
* Extras name for sync provider name
*/
public static final String EXTRAS_NAME = "name";
// --- Add-ons API
/**
@ -131,6 +136,21 @@ public class AstridApiConstants {
*/
public static final String BROADCAST_SEND_DETAILS = PACKAGE + ".SEND_DETAILS";
// --- Sync Provider API
/**
* Action name for broadcast intent requesting a listing of active
* sync actions users can activate from the menu
*/
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_RESPONSE a {@link SyncAction} to invoke synchronization
*/
public static final String BROADCAST_SEND_SYNC_ACTIONS = PACKAGE + ".SEND_SYNC_ACTIONS";
// --- Task Actions API
/**

@ -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 <tim@todoroo.com>
*
*/
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<SyncAction> CREATOR = new Parcelable.Creator<SyncAction>() {
/**
* {@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];
};
};
}

@ -12,7 +12,7 @@
<intAttribute key="com.android.ide.eclipse.adt.delay" value="0"/>
<booleanAttribute key="com.android.ide.eclipse.adt.nobootanim" value="true"/>
<intAttribute key="com.android.ide.eclipse.adt.speed" value="0"/>
<booleanAttribute key="com.android.ide.eclipse.adt.target" value="true"/>
<booleanAttribute key="com.android.ide.eclipse.adt.target" value="false"/>
<booleanAttribute key="com.android.ide.eclipse.adt.wipedata" value="false"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/astrid"/>

@ -9,8 +9,8 @@ import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import com.todoroo.andlib.data.Property.PropertyVisitor;

@ -337,7 +337,12 @@ public abstract class AbstractModel implements Parcelable {
public synchronized void save(Property<?> property, ContentValues newStore, Object value) {
this.store = newStore;
property.accept(this, value);
// we don't allow null values, as they indicate unset properties
// when the database was written
if(value != null)
property.accept(this, value);
}
public Void visitDouble(Property<Double> property, Object value) {

@ -212,6 +212,20 @@ public class GenericDao<TYPE extends AbstractModel> {
return result;
}
/**
* Updates multiple rows of the database based on model set values
*
* @param item
* item model
* @param criterion
* @return returns true on success.
*/
public int updateMultiple(ContentValues values, Criterion criterion) {
if(values.size() == 0) // nothing changed
return 0;
return database.update(table.name, values, criterion.toString(), null);
}
// --- helper methods

@ -8,6 +8,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Map.Entry;
import android.app.Activity;
@ -23,8 +25,8 @@ import android.text.InputType;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.widget.TextView;
import com.todoroo.andlib.service.Autowired;
@ -357,6 +359,18 @@ public class AndroidUtilities {
}
}
/**
* Sort files by date so the newest file is on top
* @param files
*/
public static void sortFilesByDateDesc(File[] files) {
Arrays.sort(files, new Comparator<File>() {
public int compare(File o1, File o2) {
return Long.valueOf(o2.lastModified()).compareTo(Long.valueOf(o1.lastModified()));
}
});
}
/**
* Sleep, ignoring interruption
* @param l

@ -32,6 +32,9 @@ public class DialogUtilities {
public void viewDialog(final Activity activity, final String text,
final View view, final DialogInterface.OnClickListener okListener,
final DialogInterface.OnClickListener cancelListener) {
if(activity.isFinishing())
return;
activity.runOnUiThread(new Runnable() {
public void run() {
new AlertDialog.Builder(activity)
@ -55,6 +58,9 @@ public class DialogUtilities {
*/
public void okDialog(final Activity activity, final String text,
final DialogInterface.OnClickListener okListener) {
if(activity.isFinishing())
return;
activity.runOnUiThread(new Runnable() {
public void run() {
new AlertDialog.Builder(activity)
@ -76,6 +82,9 @@ public class DialogUtilities {
*/
public void okDialog(final Activity activity, final int icon, final CharSequence text,
final DialogInterface.OnClickListener okListener) {
if(activity.isFinishing())
return;
activity.runOnUiThread(new Runnable() {
public void run() {
new AlertDialog.Builder(activity)
@ -100,6 +109,9 @@ public class DialogUtilities {
public void okCancelDialog(final Activity activity, final String title,
final String text, final DialogInterface.OnClickListener okListener,
final DialogInterface.OnClickListener cancelListener) {
if(activity.isFinishing())
return;
activity.runOnUiThread(new Runnable() {
public void run() {
new AlertDialog.Builder(activity)
@ -124,6 +136,9 @@ public class DialogUtilities {
public void okCancelDialog(final Activity activity, final String text,
final DialogInterface.OnClickListener okListener,
final DialogInterface.OnClickListener cancelListener) {
if(activity.isFinishing())
return;
activity.runOnUiThread(new Runnable() {
public void run() {
new AlertDialog.Builder(activity)

@ -34,7 +34,7 @@ public class Api4GestureDetector implements OnGesturePerformedListener {
if (predictions.size() > 0) {
Prediction prediction = predictions.get(0);
// We want at least some confidence in the result
if (prediction.score > 1.0) {
if (prediction.score > 2.0) {
listener.gesturePerformed(prediction.name);
}
}

@ -12,7 +12,6 @@ import com.timsu.astrid.R;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.DetailExposer;
import com.todoroo.astrid.model.Metadata;
/**
@ -21,7 +20,7 @@ import com.todoroo.astrid.model.Metadata;
* @author Tim Su <tim@todoroo.com>
*
*/
public class AlarmDetailExposer extends BroadcastReceiver implements DetailExposer {
public class AlarmDetailExposer extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
@ -44,7 +43,6 @@ public class AlarmDetailExposer extends BroadcastReceiver implements DetailExpos
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
}
@Override
public String getTaskDetails(Context context, long id, boolean extended) {
if(extended)
return null;
@ -71,9 +69,4 @@ public class AlarmDetailExposer extends BroadcastReceiver implements DetailExpos
}
}
@Override
public String getPluginIdentifier() {
return AlarmService.IDENTIFIER;
}
}

@ -2,9 +2,6 @@ package com.todoroo.astrid.backup;
import java.io.File;
import java.io.FilenameFilter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import android.app.AlertDialog;
import android.content.Context;
@ -12,6 +9,7 @@ import android.content.DialogInterface;
import android.util.Log;
import com.timsu.astrid.R;
import com.todoroo.andlib.utility.AndroidUtilities;
@SuppressWarnings("nls")
public class FilePickerBuilder extends AlertDialog.Builder implements DialogInterface.OnClickListener {
@ -46,11 +44,14 @@ public class FilePickerBuilder extends AlertDialog.Builder implements DialogInte
private void setPath(File path) {
if (path != null && path.exists()) {
this.path = path.getAbsolutePath();
// Reverse the order of the file list so newest time-stamped file is first.
List<String> fileList = Arrays.asList(path.list(filter));
Collections.sort(fileList);
Collections.reverse(fileList);
files = (String[]) fileList.toArray();
File[] filesAsFile = path.listFiles(filter);
AndroidUtilities.sortFilesByDateDesc(filesAsFile);
files = new String[filesAsFile.length];
for(int i = 0; i < files.length; i++)
files[i] = filesAsFile[i].getName();
setItems(files, this);
} else {
Log.e("FilePicker", "Cannot access sdcard.");

@ -50,7 +50,7 @@ public class TasksXmlExporter {
private static final int FORMAT = 2;
private final Context context;
private int exportCount;
private int exportCount = 0;
private XmlSerializer xml;
private final TaskService taskService = PluginServices.getTaskService();
private final MetadataService metadataService = PluginServices.getMetadataService();
@ -93,7 +93,11 @@ public class TasksXmlExporter {
try {
String output = setupFile(BackupConstants.getExportDirectory(),
isService);
doTasksExport(output);
int tasks = taskService.countTasks();
if(tasks > 0)
doTasksExport(output);
Preferences.setLong(BackupPreferences.PREF_BACKUP_LAST_DATE,
DateUtilities.now());
Preferences.setString(BackupPreferences.PREF_BACKUP_LAST_ERROR, null);
@ -270,12 +274,17 @@ public class TasksXmlExporter {
handler.post(new Runnable() {
@Override
public void run() {
CharSequence text = String.format(context.getString(R.string.export_toast),
context.getResources().getQuantityString(R.plurals.Ntasks, exportCount,
exportCount), outputFile);
Toast.makeText(context, text, Toast.LENGTH_LONG).show();
if(progressDialog.isShowing())
progressDialog.dismiss();
if(exportCount == 0)
Toast.makeText(context, context.getString(R.string.export_toast_no_tasks), Toast.LENGTH_LONG).show();
else {
CharSequence text = String.format(context.getString(R.string.export_toast),
context.getResources().getQuantityString(R.plurals.Ntasks, exportCount,
exportCount), outputFile);
Toast.makeText(context, text, Toast.LENGTH_LONG).show();
if(progressDialog.isShowing())
progressDialog.dismiss();
}
}
});
}

@ -41,6 +41,7 @@ import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.service.MetadataService;
import com.todoroo.astrid.service.TaskService;
import com.todoroo.astrid.tags.TagService;
import com.todoroo.astrid.utility.Flags;
public class TasksXmlImporter {
@ -145,6 +146,7 @@ public class TasksXmlImporter {
}
}
} finally {
Flags.set(Flags.REFRESH);
handler.post(new Runnable() {
@Override
public void run() {

@ -11,20 +11,16 @@ import java.util.HashMap;
import android.app.Activity;
import android.app.Notification;
import android.app.Service;
import android.content.Context;
import android.widget.Toast;
import com.timsu.astrid.R;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.data.Property.LongProperty;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.service.ExceptionService;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.service.NotificationManager;
import com.todoroo.astrid.api.TaskContainer;
import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.utility.Constants;
import com.todoroo.astrid.utility.Flags;
/**
* A helper class for writing synchronization services for Astrid. This class
@ -42,13 +38,23 @@ public abstract class SyncProvider<TYPE extends TaskContainer> {
// --- abstract methods - your services should implement these
/**
* Perform authenticate and other pre-synchronization steps, then
* synchronize.
* Perform log in (launching activity if necessary) and sync. This is
* invoked when users manually request synchronization
*
* @param context
* either the parent activity, or a background service
* @param activity
* context
*/
abstract protected void initiateManual(Activity activity);
/**
* Perform synchronize. Since this can be called from background services,
* you should not open up new activities. Instead, if the user is not signed
* in, your service should do nothing.
*
* @param service
* context
*/
abstract protected void initiate(Context context);
abstract protected void initiateBackground(Service service);
/**
* Updates the text of a notification and the intent to open when tapped
@ -128,14 +134,9 @@ public abstract class SyncProvider<TYPE extends TaskContainer> {
// --- implementation
@Autowired
private ExceptionService exceptionService;
private final Notification notification;
public SyncProvider() {
DependencyInjectionService.getInstance().inject(this);
// initialize notification
int icon = android.R.drawable.stat_notify_sync;
long when = System.currentTimeMillis();
@ -153,34 +154,24 @@ public abstract class SyncProvider<TYPE extends TaskContainer> {
Toast.LENGTH_LONG).show();
}
});
}
// display notification
updateNotification(context, notification);
final NotificationManager nm = new NotificationManager.AndroidNotificationManager(context);
nm.notify(Constants.NOTIFICATION_SYNC, notification);
// start next step in background thread
new Thread(new Runnable() {
public void run() {
try {
initiate(context);
} finally {
nm.cancel(Constants.NOTIFICATION_SYNC);
initiateManual((Activity)context);
} else if(context instanceof Service) {
// display notification
updateNotification(context, notification);
final NotificationManager nm = new NotificationManager.AndroidNotificationManager(context);
nm.notify(Constants.NOTIFICATION_SYNC, notification);
// start next step in background thread
new Thread(new Runnable() {
public void run() {
try {
initiateBackground((Service)context);
} finally {
nm.cancel(Constants.NOTIFICATION_SYNC);
}
}
}
}).start();
}
// --- utilities
/**
* Utility method for showing synchronization errors. If message is null,
* the contents of the throwable is displayed. It is assumed that the error
* was logged separately.
*/
protected void showError(final Context context, Throwable e, String message) {
exceptionService.displayAndReportError(context, message, e);
}).start();
}
}
// --- synchronization logic
@ -304,14 +295,18 @@ public abstract class SyncProvider<TYPE extends TaskContainer> {
length = data.remoteUpdated.size();
for(int i = 0; i < length; i++) {
TYPE remote = data.remoteUpdated.get(i);
// don't synchronize new & deleted / completed tasks
if(!remote.task.isSaved() && (remote.task.isDeleted() ||
remote.task.isCompleted()))
continue;
try {
write(remote);
} catch (Exception e) {
handleException("sync-remote-updated", e, false); //$NON-NLS-1$
}
}
Flags.set(Flags.REFRESH);
}
// --- helper classes

@ -1,9 +1,8 @@
package com.todoroo.astrid.api;
package com.todoroo.astrid.common;
import java.util.ArrayList;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.astrid.common.SyncProvider;
import com.todoroo.astrid.model.Metadata;
import com.todoroo.astrid.model.Task;

@ -17,6 +17,7 @@ import android.widget.Spinner;
import com.flurry.android.FlurryAgent;
import com.timsu.astrid.R;
import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.astrid.activity.AddOnActivity;
import com.todoroo.astrid.adapter.FilterAdapter;
import com.todoroo.astrid.api.Filter;
@ -44,6 +45,10 @@ public final class LocaleEditAlerts extends ExpandableListActivity {
@SuppressWarnings("nls")
public static final String KEY_SQL = "sql";
/** key name for filter content-values in bundle */
@SuppressWarnings("nls")
public static final String KEY_VALUES = "val";
/** key name for interval (integer, # of seconds) */
@SuppressWarnings("nls")
public static final String KEY_INTERVAL = "interval";
@ -262,6 +267,8 @@ public final class LocaleEditAlerts extends ExpandableListActivity {
Filter filterItem = (Filter) selected;
storeAndForwardExtras.putString(KEY_FILTER_TITLE, filterItem.title);
storeAndForwardExtras.putString(KEY_SQL, filterItem.sqlQuery);
if(filterItem.valuesForNewTasks != null)
storeAndForwardExtras.putString(KEY_VALUES, AndroidUtilities.contentValuesToSerializedString(filterItem.valuesForNewTasks));
storeAndForwardExtras.putInt(KEY_INTERVAL, INTERVALS[intervalIndex]);
returnIntent.putExtra(com.twofortyfouram.Intent.EXTRA_BUNDLE, storeAndForwardExtras);

@ -11,6 +11,7 @@ import com.timsu.astrid.R;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.activity.ShortcutActivity;
import com.todoroo.astrid.api.Filter;
@ -51,6 +52,7 @@ public class LocaleReceiver extends BroadcastReceiver {
final String title = intent.getStringExtra(LocaleEditAlerts.KEY_FILTER_TITLE);
final String sql = intent.getStringExtra(LocaleEditAlerts.KEY_SQL);
final String values = intent.getStringExtra(LocaleEditAlerts.KEY_VALUES);
final int interval = intent.getIntExtra(LocaleEditAlerts.KEY_INTERVAL, 24*3600);
if(TextUtils.isEmpty(title) || TextUtils.isEmpty(sql) ||
@ -74,6 +76,10 @@ public class LocaleReceiver extends BroadcastReceiver {
try {
if(cursor.getCount() == 0)
return;
if(values != null)
filter.valuesForNewTasks = AndroidUtilities.contentValuesFromSerializedString(values);
Resources r = context.getResources();
String reminder = r.getString(R.string.locale_notification).
replace("$NUM", r.getQuantityString(R.plurals.Ntasks,

@ -10,7 +10,6 @@ import android.content.Intent;
import com.timsu.astrid.R;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.DetailExposer;
import com.todoroo.astrid.core.PluginServices;
import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.utility.Preferences;
@ -21,7 +20,7 @@ import com.todoroo.astrid.utility.Preferences;
* @author Tim Su <tim@todoroo.com>
*
*/
public class NoteDetailExposer extends BroadcastReceiver implements DetailExposer {
public class NoteDetailExposer extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
@ -31,7 +30,7 @@ public class NoteDetailExposer extends BroadcastReceiver implements DetailExpose
return;
boolean extended = intent.getBooleanExtra(AstridApiConstants.EXTRAS_EXTENDED, false);
String taskDetail = getTaskDetails(context, taskId, extended);
String taskDetail = getTaskDetails(taskId, extended);
if(taskDetail == null)
return;
@ -44,8 +43,7 @@ public class NoteDetailExposer extends BroadcastReceiver implements DetailExpose
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
}
@Override
public String getTaskDetails(Context context, long id, boolean extended) {
public String getTaskDetails(long id, boolean extended) {
if(Preferences.getBoolean(R.string.p_showNotes, false)) {
if(extended)
@ -65,9 +63,4 @@ public class NoteDetailExposer extends BroadcastReceiver implements DetailExpose
return "<img src='silk_note'/> " + notes; //$NON-NLS-1$
}
@Override
public String getPluginIdentifier() {
return NotesPlugin.IDENTIFIER;
}
}

@ -39,7 +39,8 @@ public class ProducteevBackgroundService extends Service {
@Override
public void onStart(Intent intent, int startId) {
try {
startSynchronization(this);
if(intent != null && SYNC_ACTION.equals(intent.getAction()))
startSynchronization(this);
} catch (Exception e) {
PluginServices.getExceptionService().reportError("pdv-bg-sync", e); //$NON-NLS-1$
}

@ -2,7 +2,12 @@ package com.todoroo.astrid.producteev;
import java.util.ArrayList;
import org.json.JSONObject;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@ -10,6 +15,7 @@ import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
@ -17,12 +23,14 @@ import com.timsu.astrid.R;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.DialogUtilities;
import com.todoroo.astrid.activity.TaskEditActivity.TaskEditControlSet;
import com.todoroo.astrid.model.Metadata;
import com.todoroo.astrid.model.StoreObject;
import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.producteev.sync.ProducteevDashboard;
import com.todoroo.astrid.producteev.sync.ProducteevDataService;
import com.todoroo.astrid.producteev.sync.ProducteevSyncProvider;
import com.todoroo.astrid.producteev.sync.ProducteevTask;
import com.todoroo.astrid.producteev.sync.ProducteevUser;
import com.todoroo.astrid.service.MetadataService;
@ -38,6 +46,7 @@ public class ProducteevControlSet implements TaskEditControlSet {
// --- instance variables
private final Activity activity;
private final DialogUtilities dialogUtilites;
private final View view;
private Task myTask;
@ -50,10 +59,14 @@ public class ProducteevControlSet implements TaskEditControlSet {
@Autowired
MetadataService metadataService;
private int lastDashboardSelection = 0;
public ProducteevControlSet(final Activity activity, ViewGroup parent) {
DependencyInjectionService.getInstance().inject(this);
this.activity = activity;
this.dialogUtilites = new DialogUtilities();
view = LayoutInflater.from(activity).inflate(R.layout.producteev_control, parent, true);
this.responsibleSelector = (Spinner) activity.findViewById(R.id.producteev_TEA_task_assign);
@ -67,9 +80,68 @@ public class ProducteevControlSet implements TaskEditControlSet {
@Override
public void onItemSelected(AdapterView<?> spinnerParent, View spinnerView,
int position, long id) {
Spinner dashSelector = (Spinner) spinnerParent;
final Spinner dashSelector = (Spinner) spinnerParent;
ProducteevDashboard dashboard = (ProducteevDashboard) dashSelector.getSelectedItem();
refreshResponsibleSpinner(dashboard.getUsers());
if (dashboard.getId() == ProducteevUtilities.DASHBOARD_CREATE) {
// let the user create a new dashboard
final EditText editor = new EditText(ProducteevControlSet.this.activity);
OnClickListener okListener = new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Activity context = ProducteevControlSet.this.activity;
String newDashboardName = editor.getText().toString();
if (newDashboardName == null || newDashboardName.length() == 0) {
dialog.cancel();
} else {
// create the real dashboard, select it in the spinner and refresh responsiblespinner
ProgressDialog progressDialog = dialogUtilites.progressDialog(context,
context.getString(R.string.DLG_wait));
try {
progressDialog.show();
JSONObject newDashJSON = ProducteevSyncProvider.getInvoker().dashboardsCreate(
newDashboardName).getJSONObject("dashboard"); //$NON-NLS-1$
StoreObject local = ProducteevDataService.getInstance().updateDashboards(newDashJSON, true);
if (local != null) {
ProducteevDashboard newDashboard = new ProducteevDashboard(local);
ArrayAdapter<ProducteevDashboard> adapter = (ArrayAdapter<ProducteevDashboard>) dashSelector.getAdapter();
adapter.insert(newDashboard, adapter.getCount()-1);
dashSelector.setSelection(adapter.getCount()-2);
refreshResponsibleSpinner(newDashboard.getUsers());
dialogUtilites.dismissDialog(context, progressDialog);
}
} catch (Exception e) {
dialogUtilites.dismissDialog(context, progressDialog);
dialogUtilites.okDialog(context,
context.getString(R.string.DLG_error, e.getMessage()),
new OnClickListener() {
@Override
public void onClick(DialogInterface theDialog, int theWhich) {
theDialog.dismiss();
}
});
e.printStackTrace();
dashSelector.setSelection(0);
}
}
}
};
OnClickListener cancelListener = new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
dashboardSelector.setSelection(lastDashboardSelection);
}
};
dialogUtilites.viewDialog(ProducteevControlSet.this.activity,
ProducteevControlSet.this.activity.getString(R.string.producteev_create_dashboard_name),
editor,
okListener,
cancelListener);
} else {
refreshResponsibleSpinner(dashboard.getUsers());
lastDashboardSelection = position;
}
}
@Override
@ -140,8 +212,8 @@ public class ProducteevControlSet implements TaskEditControlSet {
ProducteevDashboard ownerDashboard = null;
int dashboardSpinnerIndex = -1;
//dashboard to not sync as first spinner-entry
for (int i=0;i<dashboardsData.length;i++) {
int i = 0;
for (i=0;i<dashboardsData.length;i++) {
ProducteevDashboard dashboard = new ProducteevDashboard(dashboardsData[i]);
dashboards.add(dashboard);
if(dashboard.getId() == dashboardId) {
@ -149,7 +221,11 @@ public class ProducteevControlSet implements TaskEditControlSet {
dashboardSpinnerIndex = i;
}
}
//dashboard to not sync as first spinner-entry
dashboards.add(0, new ProducteevDashboard(ProducteevUtilities.DASHBOARD_NO_SYNC, activity.getString(R.string.producteev_no_dashboard),null));
// dummyentry for adding a new dashboard
dashboards.add(new ProducteevDashboard(ProducteevUtilities.DASHBOARD_CREATE, activity.getString(R.string.producteev_create_dashboard),null));
ArrayAdapter<ProducteevDashboard> dashAdapter = new ArrayAdapter<ProducteevDashboard>(activity,
android.R.layout.simple_spinner_item, dashboards);
@ -157,7 +233,8 @@ public class ProducteevControlSet implements TaskEditControlSet {
dashboardSelector.setAdapter(dashAdapter);
dashboardSelector.setSelection(dashboardSpinnerIndex+1);
if (ownerDashboard == null || ownerDashboard.getId() == ProducteevUtilities.DASHBOARD_NO_SYNC) {
if (ownerDashboard == null || ownerDashboard.getId() == ProducteevUtilities.DASHBOARD_NO_SYNC
|| ownerDashboard.getId() == ProducteevUtilities.DASHBOARD_CREATE) {
responsibleSelector.setEnabled(false);
responsibleSelector.setAdapter(null);
view.findViewById(R.id.producteev_TEA_task_assign_label).setVisibility(View.GONE);

@ -7,10 +7,10 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import com.timsu.astrid.R;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.astrid.adapter.TaskAdapter;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.DetailExposer;
import com.todoroo.astrid.model.Metadata;
import com.todoroo.astrid.model.StoreObject;
import com.todoroo.astrid.producteev.sync.ProducteevDashboard;
@ -26,7 +26,7 @@ import com.todoroo.astrid.utility.Preferences;
* @author Tim Su <tim@todoroo.com>
*
*/
public class ProducteevDetailExposer extends BroadcastReceiver implements DetailExposer{
public class ProducteevDetailExposer extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
@ -51,7 +51,6 @@ public class ProducteevDetailExposer extends BroadcastReceiver implements Detail
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
}
@Override
public String getTaskDetails(Context context, long id, boolean extended) {
Metadata metadata = ProducteevDataService.getInstance().getTaskMetadata(id);
if(metadata == null)
@ -64,6 +63,9 @@ public class ProducteevDetailExposer extends BroadcastReceiver implements Detail
long responsibleId = -1;
if(metadata.containsNonNullValue(ProducteevTask.RESPONSIBLE_ID))
responsibleId = metadata.getValue(ProducteevTask.RESPONSIBLE_ID);
long creatorId = -1;
if(metadata.containsNonNullValue(ProducteevTask.CREATOR_ID))
creatorId = metadata.getValue(ProducteevTask.CREATOR_ID);
// display dashboard if not "no sync" or "default"
StoreObject ownerDashboard = null;
@ -86,14 +88,21 @@ public class ProducteevDetailExposer extends BroadcastReceiver implements Detail
// display responsible user if not current one
if(responsibleId > 0 && ownerDashboard != null && responsibleId !=
Preferences.getLong(ProducteevUtilities.PREF_USER_ID, 0L)) {
String users = ";" + ownerDashboard.getValue(ProducteevDashboard.USERS); //$NON-NLS-1$
int index = users.indexOf(";" + responsibleId + ","); //$NON-NLS-1$ //$NON-NLS-2$
if(index > -1) {
String user = users.substring(users.indexOf(',', index) + 1,
users.indexOf(';', index + 1));
String user = getUserFromDashboard(ownerDashboard, responsibleId);
if(user != null)
builder.append("<img src='silk_user_gray'/> ").append(user).append(TaskAdapter.DETAIL_SEPARATOR); //$NON-NLS-1$
}
}
// display creator user if not the current one
if(creatorId > 0 && ownerDashboard != null && creatorId !=
Preferences.getLong(ProducteevUtilities.PREF_USER_ID, 0L)) {
String user = getUserFromDashboard(ownerDashboard, creatorId);
if(user != null)
builder.append("<img src='silk_user_orange'/> ").append( //$NON-NLS-1$
context.getString(R.string.producteev_PDE_task_from, user)).
append(TaskAdapter.DETAIL_SEPARATOR);
}
} else {
TodorooCursor<Metadata> notesCursor = ProducteevDataService.getInstance().getTaskNotesCursor(id);
try {
@ -112,9 +121,14 @@ public class ProducteevDetailExposer extends BroadcastReceiver implements Detail
return result.substring(0, result.length() - TaskAdapter.DETAIL_SEPARATOR.length());
}
@Override
public String getPluginIdentifier() {
return ProducteevUtilities.IDENTIFIER;
/** Try and find user in the dashboard. return null if un-findable */
private String getUserFromDashboard(StoreObject dashboard, long userId) {
String users = ";" + dashboard.getValue(ProducteevDashboard.USERS); //$NON-NLS-1$
int index = users.indexOf(";" + userId + ","); //$NON-NLS-1$ //$NON-NLS-2$
if(index > -1)
return users.substring(users.indexOf(',', index) + 1,
users.indexOf(';', index + 1));
return null;
}
}

@ -3,8 +3,8 @@
*/
package com.todoroo.astrid.producteev;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.Map.Entry;
import android.content.BroadcastReceiver;
import android.content.ContentValues;
@ -39,7 +39,7 @@ public class ProducteevFilterExposer extends BroadcastReceiver {
/**
* @param context
*/
private Filter filterFromList(Context context, ProducteevDashboard dashboard) {
public static Filter filterFromList(Context context, ProducteevDashboard dashboard) {
String dashboardTitle = dashboard.getName();
String title = dashboard.getName();
ContentValues values = new ContentValues();

@ -111,21 +111,15 @@ public class ProducteevLoginActivity extends Activity {
else {
Editable email = emailEditText.getText();
Editable password = passwordEditText.getText();
Editable confirmPassword = ((EditText)findViewById(R.id.confirmPassword)).getText();
Editable firstName = ((EditText)findViewById(R.id.firstName)).getText();
Editable lastName = ((EditText)findViewById(R.id.lastName)).getText();
if(email.length() == 0 || password.length() == 0 ||
confirmPassword.length() == 0 || firstName.length() == 0 ||
firstName.length() == 0 ||
lastName.length() == 0) {
errors.setVisibility(View.VISIBLE);
errors.setText(R.string.producteev_PLA_errorEmpty);
return;
}
if(!confirmPassword.toString().equals(password.toString())) {
errors.setVisibility(View.VISIBLE);
errors.setText(R.string.producteev_PLA_errorMatch);
return;
}
performSignup(email.toString(), password.toString(),
firstName.toString(), lastName.toString());
}

@ -1,6 +1,5 @@
package com.todoroo.astrid.producteev;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.preference.ListPreference;
@ -31,8 +30,7 @@ public class ProducteevPreferences extends SyncProviderPreferences {
@Override
public void startSync() {
startService(new Intent(ProducteevBackgroundService.SYNC_ACTION, null,
this, ProducteevBackgroundService.class));
new ProducteevSyncProvider().synchronize(this);
}
@Override

@ -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);
}
}

@ -18,6 +18,9 @@ public class ProducteevUtilities extends SyncProviderUtilities {
public static final ProducteevUtilities INSTANCE = new ProducteevUtilities();
/** setting for dashboard to getting created */
public static final int DASHBOARD_CREATE = -2;
/** setting for dashboard to not synchronize */
public static final int DASHBOARD_NO_SYNC = -1;
@ -38,6 +41,10 @@ public class ProducteevUtilities extends SyncProviderUtilities {
public static final String PREF_SERVER_LAST_SYNC = IDENTIFIER + "_last_server"; //$NON-NLS-1$
public static final String PREF_SERVER_LAST_NOTIFICATION = IDENTIFIER + "_last_notification"; //$NON-NLS-1$
public static final String PREF_SERVER_LAST_ACTIVITY = IDENTIFIER + "_last_activity"; //$NON-NLS-1$
/** Producteev user's default dashboard. This is different from the
* preference key, which indicates where user wants to put new tasks */
public static final String PREF_DEFAULT_DASHBOARD = IDENTIFIER + "_defaultdash"; //$NON-NLS-1$

@ -111,6 +111,31 @@ public class ProducteevInvoker {
"since", since), "dashboards");
}
/**
* create a dasbhoard
*
* @param name
* @return the new created dashboard as JSONObject
*/
public JSONObject dashboardsCreate(String name) throws ApiServiceException, IOException {
return callAuthenticated("dashboards/create.json",
"token", token,
"title", name);
}
/**
* return the list of users who can access a specific dashboard
*
* @param idDashboard
* @param dashboard array-information about the dashboard, if this ...
*/
public JSONArray dashboardsAccess(long idDashboard, String dashboard) throws ApiServiceException, IOException {
return getResponse(callAuthenticated("dashboards/access.json",
"token", token,
"id_dashboard", idDashboard,
"dashboard", dashboard),"dashboard");
}
// --- tasks
/**
@ -217,14 +242,16 @@ public class ProducteevInvoker {
*
* @param idTask
* @param deadline
* @param allDay (optional), 1: all day task (time specified in deadline will be ignored), 0: deadline with time
*
* @return array tasks/view
*/
public JSONObject tasksSetDeadline(long idTask, String deadline) throws ApiServiceException, IOException {
public JSONObject tasksSetDeadline(long idTask, String deadline, Integer allDay) throws ApiServiceException, IOException {
return callAuthenticated("tasks/set_deadline.json",
"token", token,
"id_task", idTask,
"deadline", deadline);
"deadline", deadline,
"all_day", allDay);
}
/**
@ -374,6 +401,36 @@ public class ProducteevInvoker {
"title", title);
}
// --- notifications/activities
/**
* get every activities
*
* @param dashboardId (optional) if not null, this function only returns notifications for this specific dashboard
* @param lastId (optional) this function returns only activities later than this id
*/
public JSONArray activitiesShowActivities(Long dashboardId, Long lastId) throws ApiResponseParseException, ApiServiceException, IOException {
return getResponse(callAuthenticated("activities/show_activities.json",
"token", token,
"id_dashboard", dashboardId,
"last_id", lastId), "activities");
}
/**
* get every notification for the current user
* @param dashboardId
* @param lastId
* @return
* @throws ApiResponseParseException
* @throws ApiServiceException
* @throws IOException
*/
public JSONArray activitiesShowNotifications(Long dashboardId, Long lastId) throws ApiResponseParseException, ApiServiceException, IOException {
return getResponse(callAuthenticated("activities/show_notifications.json",
"token", token,
"id_dashboard", dashboardId,
"last_id", lastId), "activities");
}
// --- users
/**
@ -389,19 +446,6 @@ public class ProducteevInvoker {
"id_colleague", idColleague);
}
/**
* return the list of users who can access a specific dashboard
*
* @param idDashboard
* @param dashboard array-information about the dashboard, if this ...
*/
public JSONArray dashboardsAccess(long idDashboard, String dashboard) throws ApiServiceException, IOException {
return getResponse(callAuthenticated("dashboards/access",
"token", token,
"id_dashboard", idDashboard,
"dashboard", dashboard),"dashboard");
}
// --- invocation
private final ProducteevRestClient restClient = new ProducteevRestClient();

@ -38,7 +38,7 @@ public class ProducteevDashboard {
private ArrayList<ProducteevUser> users = null;
public ProducteevDashboard (StoreObject dashboardData) {
this(dashboardData.getValue(REMOTE_ID),dashboardData.getValue(NAME),dashboardData.getValue(USERS));
this(dashboardData.getValue(REMOTE_ID),dashboardData.getValue(NAME),(dashboardData.containsValue(USERS)?dashboardData.getValue(USERS):null));
}
/**

@ -20,15 +20,17 @@ import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Join;
import com.todoroo.andlib.sql.Query;
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
import com.todoroo.astrid.core.PluginServices;
import com.todoroo.astrid.dao.StoreObjectDao;
import com.todoroo.astrid.dao.StoreObjectDao.StoreObjectCriteria;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
import com.todoroo.astrid.dao.StoreObjectDao.StoreObjectCriteria;
import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
import com.todoroo.astrid.model.Metadata;
import com.todoroo.astrid.model.StoreObject;
import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.producteev.ProducteevUtilities;
import com.todoroo.astrid.producteev.api.ApiUtilities;
import com.todoroo.astrid.service.MetadataService;
import com.todoroo.astrid.tags.TagService;
@ -82,6 +84,7 @@ public final class ProducteevDataService {
metadataService.deleteWhere(Metadata.KEY.eq(ProducteevTask.METADATA_KEY));
metadataService.deleteWhere(Metadata.KEY.eq(ProducteevNote.METADATA_KEY));
storeObjectDao.deleteWhere(StoreObject.TYPE.eq(ProducteevDashboard.TYPE));
PluginServices.getTaskService().clearDetails();
}
/**
@ -244,41 +247,50 @@ public final class ProducteevDataService {
readDashboards();
for(int i = 0; i < changedDashboards.length(); i++) {
JSONObject remote = changedDashboards.getJSONObject(i).getJSONObject("dashboard");
long id = remote.getLong("id_dashboard");
StoreObject local = null;
for(StoreObject dashboard : dashboards) {
if(dashboard.getValue(ProducteevDashboard.REMOTE_ID).equals(id)) {
local = dashboard;
break;
}
}
updateDashboards(remote, false);
}
if(remote.getInt("deleted") != 0) {
if(local != null)
storeObjectDao.delete(local.getId());
continue;
}
// clear dashboard cache
dashboards = null;
}
if(local == null)
local = new StoreObject();
local.setValue(StoreObject.TYPE, ProducteevDashboard.TYPE);
local.setValue(ProducteevDashboard.REMOTE_ID, id);
local.setValue(ProducteevDashboard.NAME, remote.getString("title"));
StringBuilder users = new StringBuilder();
JSONArray accessList = remote.getJSONArray("accesslist");
for(int j = 0; j < accessList.length(); j++) {
JSONObject user = accessList.getJSONObject(j).getJSONObject("user");
users.append(user.getLong("id_user")).append(',');
String name = user.optString("firstname", "") + ' ' +
user.optString("lastname", "");
users.append(name.trim()).append(';');
@SuppressWarnings("nls")
public StoreObject updateDashboards(JSONObject remote, boolean reinitCache) throws JSONException {
if (reinitCache)
readDashboards();
long id = remote.getLong("id_dashboard");
StoreObject local = null;
for(StoreObject dashboard : dashboards) {
if(dashboard.getValue(ProducteevDashboard.REMOTE_ID).equals(id)) {
local = dashboard;
break;
}
local.setValue(ProducteevDashboard.USERS, users.toString());
storeObjectDao.persist(local);
}
// clear dashboard cache
dashboards = null;
if(remote.getInt("deleted") != 0) {
if(local != null)
storeObjectDao.delete(local.getId());
}
if(local == null)
local = new StoreObject();
local.setValue(StoreObject.TYPE, ProducteevDashboard.TYPE);
local.setValue(ProducteevDashboard.REMOTE_ID, id);
local.setValue(ProducteevDashboard.NAME, ApiUtilities.decode(remote.getString("title")));
StringBuilder users = new StringBuilder();
JSONArray accessList = remote.getJSONArray("accesslist");
for(int j = 0; j < accessList.length(); j++) {
JSONObject user = accessList.getJSONObject(j).getJSONObject("user");
users.append(user.getLong("id_user")).append(',');
String name = ApiUtilities.decode(user.optString("firstname", "") + ' ' +
user.optString("lastname", ""));
users.append(name.trim()).append(';');
}
local.setValue(ProducteevDashboard.USERS, users.toString());
storeObjectDao.persist(local);
if (reinitCache)
dashboards = null;
return local;
}
}

@ -15,6 +15,7 @@ import org.json.JSONObject;
import android.app.Activity;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
@ -27,15 +28,20 @@ import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.service.ExceptionService;
import com.todoroo.andlib.service.NotificationManager;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.DialogUtilities;
import com.todoroo.astrid.activity.ShortcutActivity;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.TaskContainer;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.common.SyncProvider;
import com.todoroo.astrid.common.TaskContainer;
import com.todoroo.astrid.model.Metadata;
import com.todoroo.astrid.model.StoreObject;
import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.producteev.ProducteevBackgroundService;
import com.todoroo.astrid.producteev.ProducteevFilterExposer;
import com.todoroo.astrid.producteev.ProducteevLoginActivity;
import com.todoroo.astrid.producteev.ProducteevPreferences;
import com.todoroo.astrid.producteev.ProducteevUtilities;
@ -45,6 +51,7 @@ import com.todoroo.astrid.producteev.api.ApiUtilities;
import com.todoroo.astrid.producteev.api.ProducteevInvoker;
import com.todoroo.astrid.service.AstridDependencyInjector;
import com.todoroo.astrid.tags.TagService;
import com.todoroo.astrid.utility.Constants;
import com.todoroo.astrid.utility.Preferences;
@SuppressWarnings("nls")
@ -74,7 +81,7 @@ public class ProducteevSyncProvider extends SyncProvider<ProducteevTaskContainer
}
// ----------------------------------------------------------------------
// ------------------------------------------------------- public methods
// ------------------------------------------------------ utility methods
// ----------------------------------------------------------------------
/**
@ -84,16 +91,15 @@ public class ProducteevSyncProvider extends SyncProvider<ProducteevTaskContainer
preferences.setToken(null);
Preferences.setString(R.string.producteev_PPr_email, null);
Preferences.setString(R.string.producteev_PPr_password, null);
Preferences.setString(ProducteevUtilities.PREF_SERVER_LAST_SYNC, null);
Preferences.setStringFromInteger(R.string.producteev_PPr_defaultdash_key,
ProducteevUtilities.DASHBOARD_DEFAULT);
preferences.clearLastSyncDate();
dataService = ProducteevDataService.getInstance();
dataService.clearMetadata();
}
// ----------------------------------------------------------------------
// ------------------------------------------------------- authentication
// ----------------------------------------------------------------------
/**
* Deal with a synchronization exception. If requested, will show an error
* to the user (unless synchronization is happening in background)
@ -107,44 +113,40 @@ public class ProducteevSyncProvider extends SyncProvider<ProducteevTaskContainer
* whether to display a dialog
*/
@Override
protected void handleException(String tag, Exception e, boolean showError) {
protected void handleException(String tag, Exception e, boolean displayError) {
final Context context = ContextManager.getContext();
preferences.setLastError(e.toString());
String message = null;
// occurs when application was closed
if(e instanceof IllegalStateException) {
exceptionService.reportError(tag + "-caught", e); //$NON-NLS-1$
// occurs when network error
// occurs when network error
} else if(!(e instanceof ApiServiceException) && e instanceof IOException) {
exceptionService.reportError(tag + "-ioexception", e); //$NON-NLS-1$
if(showError) {
Context context = ContextManager.getContext();
showError(context, e, context.getString(R.string.producteev_ioerror));
}
message = context.getString(R.string.producteev_ioerror);
} else {
message = context.getString(R.string.DLG_error, e.toString());
exceptionService.reportError(tag + "-unhandled", e); //$NON-NLS-1$
if(showError) {
Context context = ContextManager.getContext();
showError(context, e, null);
}
}
}
@Override
protected void initiate(Context context) {
dataService = ProducteevDataService.getInstance();
// authenticate the user. this will automatically call the next step
authenticate();
if(displayError && context instanceof Activity && message != null) {
dialogUtilities.okDialog((Activity)context,
message, null);
}
}
// ----------------------------------------------------------------------
// ------------------------------------------------------ initiating sync
// ----------------------------------------------------------------------
/**
* Perform authentication with RTM. Will open the SyncBrowser if necessary
* initiate sync in background
*/
private void authenticate() {
FlurryAgent.onEvent("producteev-started");
preferences.recordSyncStart();
@Override
protected void initiateBackground(Service service) {
dataService = ProducteevDataService.getInstance();
try {
String authToken = preferences.getToken();
@ -159,16 +161,7 @@ public class ProducteevSyncProvider extends SyncProvider<ProducteevTaskContainer
performSync();
} else {
if (email == null && password == null) {
// display login-activity
final Context context = ContextManager.getContext();
Intent intent = new Intent(context, ProducteevLoginActivity.class);
if(context instanceof Activity)
((Activity)context).startActivityForResult(intent, 0);
else {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
ProducteevUtilities.INSTANCE.stopOngoing();
}
// we can't do anything, user is not logged in
} else {
invoker.authenticate(email, password);
preferences.setToken(invoker.getToken());
@ -176,7 +169,7 @@ public class ProducteevSyncProvider extends SyncProvider<ProducteevTaskContainer
}
}
} catch (IllegalStateException e) {
// occurs when application was closed
// occurs when application was closed
} catch (Exception e) {
handleException("pdv-authenticate", e, true);
} finally {
@ -184,6 +177,25 @@ public class ProducteevSyncProvider extends SyncProvider<ProducteevTaskContainer
}
}
/**
* If user isn't already signed in, show sign in dialog. Else perform sync.
*/
@Override
protected void initiateManual(Activity activity) {
String authToken = preferences.getToken();
ProducteevUtilities.INSTANCE.stopOngoing();
// check if we have a token & it works
if(authToken == null) {
// display login-activity
Intent intent = new Intent(activity, ProducteevLoginActivity.class);
activity.startActivityForResult(intent, 0);
} else {
activity.startService(new Intent(ProducteevBackgroundService.SYNC_ACTION, null,
activity, ProducteevBackgroundService.class));
}
}
public static ProducteevInvoker getInvoker() {
String z = stripslashes(0, "71o3346pr40o5o4nt4n7t6n287t4op28","2");
String v = stripslashes(2, "9641n76n9s1736q1578q1o1337q19233","4ae");
@ -195,6 +207,9 @@ public class ProducteevSyncProvider extends SyncProvider<ProducteevTaskContainer
// ----------------------------------------------------------------------
protected void performSync() {
FlurryAgent.onEvent("producteev-started");
preferences.recordSyncStart();
try {
// load user information
JSONObject user = invoker.usersView(null).getJSONObject("user");
@ -202,9 +217,11 @@ public class ProducteevSyncProvider extends SyncProvider<ProducteevTaskContainer
long userId = user.getLong("id_user");
String lastServerSync = Preferences.getStringValue(ProducteevUtilities.PREF_SERVER_LAST_SYNC);
String lastNotificationId = Preferences.getStringValue(ProducteevUtilities.PREF_SERVER_LAST_NOTIFICATION);
String lastActivityId = Preferences.getStringValue(ProducteevUtilities.PREF_SERVER_LAST_ACTIVITY);
// read dashboards
JSONArray dashboards = invoker.dashboardsShowList(lastServerSync);
JSONArray dashboards = invoker.dashboardsShowList(null);
dataService.updateDashboards(dashboards);
// read labels and tasks for each dashboard
@ -217,6 +234,9 @@ public class ProducteevSyncProvider extends SyncProvider<ProducteevTaskContainer
JSONArray tasks = invoker.tasksShowList(dashboardId, lastServerSync);
for(int i = 0; i < tasks.length(); i++) {
ProducteevTaskContainer remote = parseRemoteTask(tasks.getJSONObject(i));
// update reminder flags for incoming remote tasks to prevent annoying
if(remote.task.hasDueDate() && remote.task.getValue(Task.DUE_DATE) < DateUtilities.now())
remote.task.setFlag(Task.REMINDER_FLAGS, Task.NOTIFY_AFTER_DEADLINE, false);
boolean foundLocal = dataService.findLocalMatch(remote);
// if creator & responsible != current user, skip / delete it
@ -246,6 +266,56 @@ public class ProducteevSyncProvider extends SyncProvider<ProducteevTaskContainer
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_EVENT_REFRESH);
ContextManager.getContext().sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
// notification/activities stuff
JSONArray notifications = invoker.activitiesShowNotifications(null, (lastNotificationId == null ? null : new Long(lastNotificationId)));
String[] notificationsList = parseActivities(notifications);
// update lastIds
if (notifications.length() > 0) {
lastNotificationId = ""+notifications.getJSONObject(0).getJSONObject("activity").getLong("id_activity");
}
// display notifications from producteev-log
Context context = ContextManager.getContext();
final NotificationManager nm = new NotificationManager.AndroidNotificationManager(context);
for (int i = 0; i< notificationsList.length; i++) {
long id_dashboard = notifications.getJSONObject(i).getJSONObject("activity").getLong("id_dashboard");
String dashboardName = null;
StoreObject[] dashboardsData = ProducteevDataService.getInstance().getDashboards();
ProducteevDashboard dashboard = null;
if (dashboardsData != null) {
for (int j=0; i<dashboardsData.length;i++) {
long id = dashboardsData[j].getValue(ProducteevDashboard.REMOTE_ID);
if (id == id_dashboard) {
dashboardName = dashboardsData[j].getValue(ProducteevDashboard.NAME);
dashboard = new ProducteevDashboard(id, dashboardName, null);
break;
}
}
}
// it seems dashboard is null if we get a notification about an unknown dashboard, just filter it.
if (dashboard != null) {
// initialize notification
int icon = R.drawable.ic_producteev_notification;
long when = System.currentTimeMillis();
Notification notification = new Notification(icon, null, when);
CharSequence contentTitle = context.getString(R.string.producteev_notification_title)+": "+dashboard.getName();
Filter filter = ProducteevFilterExposer.filterFromList(context, dashboard);
Intent notificationIntent = ShortcutActivity.createIntent(filter);
// filter the tags from the message
String message = notificationsList[i].replaceAll("<[^>]+>", "");
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
notification.setLatestEventInfo(context, contentTitle, message, contentIntent);
nm.notify(Constants.NOTIFICATION_PRODUCTEEV_NOTIFICATIONS-i, notification);
}
}
// store lastIds in Preferences
Preferences.setString(ProducteevUtilities.PREF_SERVER_LAST_NOTIFICATION, lastNotificationId);
Preferences.setString(ProducteevUtilities.PREF_SERVER_LAST_ACTIVITY, lastActivityId);
FlurryAgent.onEvent("pdv-sync-finished"); //$NON-NLS-1$
} catch (IllegalStateException e) {
// occurs when application was closed
@ -254,6 +324,24 @@ public class ProducteevSyncProvider extends SyncProvider<ProducteevTaskContainer
}
}
/**
* @param activities
* @return
* @throws JSONException
*/
private String[] parseActivities(JSONArray activities) throws JSONException {
int count = (activities == null ? 0 : activities.length());
String[] activitiesList = new String[count];
if(activities == null)
return activitiesList;
for(int i = 0; i < activities.length(); i++) {
String message = activities.getJSONObject(i).getJSONObject("activity").getString("message");
activitiesList[i] = ApiUtilities.decode(message);
}
return activitiesList;
}
// ----------------------------------------------------------------------
// ------------------------------------------------------------ sync data
// ----------------------------------------------------------------------
@ -263,6 +351,12 @@ public class ProducteevSyncProvider extends SyncProvider<ProducteevTaskContainer
long userId = user.getLong("id_user");
Preferences.setLong(ProducteevUtilities.PREF_DEFAULT_DASHBOARD, defaultDashboard);
Preferences.setLong(ProducteevUtilities.PREF_USER_ID, userId);
// save the default dashboard preference if unset
int defaultDashSetting = Preferences.getIntegerFromString(R.string.producteev_PPr_defaultdash_key,
ProducteevUtilities.DASHBOARD_DEFAULT);
if(defaultDashSetting == ProducteevUtilities.DASHBOARD_DEFAULT)
Preferences.setStringFromInteger(R.string.producteev_PPr_defaultdash_key, (int) defaultDashboard);
}
// all synchronized properties
@ -302,6 +396,7 @@ public class ProducteevSyncProvider extends SyncProvider<ProducteevTaskContainer
long dashboard = ProducteevUtilities.INSTANCE.getDefaultDashboard();
if(local.pdvTask.containsNonNullValue(ProducteevTask.DASHBOARD_ID))
dashboard = local.pdvTask.getValue(ProducteevTask.DASHBOARD_ID);
long responsibleId = local.pdvTask.getValue(ProducteevTask.RESPONSIBLE_ID);
if(dashboard == ProducteevUtilities.DASHBOARD_NO_SYNC) {
// set a bogus task id, then return without creating
@ -310,7 +405,7 @@ public class ProducteevSyncProvider extends SyncProvider<ProducteevTaskContainer
}
JSONObject response = invoker.tasksCreate(localTask.getValue(Task.TITLE),
null, dashboard, createDeadline(localTask), createReminder(localTask),
responsibleId, dashboard, createDeadline(localTask), createReminder(localTask),
localTask.isCompleted() ? 2 : 1, createStars(localTask));
ProducteevTaskContainer newRemoteTask;
try {
@ -338,7 +433,10 @@ public class ProducteevSyncProvider extends SyncProvider<ProducteevTaskContainer
task.setValue(Task.DELETION_DATE, remoteTask.getInt("deleted") == 1 ? DateUtilities.now() : 0);
long dueDate = ApiUtilities.producteevToUnixTime(remoteTask.getString("deadline"), 0);
task.setValue(Task.DUE_DATE, task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, dueDate));
if(remoteTask.optInt("all_day", 0) == 1)
task.setValue(Task.DUE_DATE, task.createDueDate(Task.URGENCY_SPECIFIC_DAY, dueDate));
else
task.setValue(Task.DUE_DATE, task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, dueDate));
task.setValue(Task.IMPORTANCE, 5 - remoteTask.getInt("star"));
JSONArray labels = remoteTask.getJSONArray("labels");
@ -386,6 +484,7 @@ public class ProducteevSyncProvider extends SyncProvider<ProducteevTaskContainer
protected void push(ProducteevTaskContainer local, ProducteevTaskContainer remote) throws IOException {
long idTask = local.pdvTask.getValue(ProducteevTask.ID);
long idDashboard = local.pdvTask.getValue(ProducteevTask.DASHBOARD_ID);
long idResponsible = local.pdvTask.getValue(ProducteevTask.RESPONSIBLE_ID);
// if local is marked do not sync, handle accordingly
if(idDashboard == ProducteevUtilities.DASHBOARD_NO_SYNC) {
@ -420,13 +519,19 @@ public class ProducteevSyncProvider extends SyncProvider<ProducteevTaskContainer
remote = create(local);
}
// responsible
if(remote != null && idResponsible !=
remote.pdvTask.getValue(ProducteevTask.RESPONSIBLE_ID)) {
invoker.tasksSetResponsible(idTask, idResponsible);
}
// core properties
if(shouldTransmit(local, Task.TITLE, remote))
invoker.tasksSetTitle(idTask, local.task.getValue(Task.TITLE));
if(shouldTransmit(local, Task.IMPORTANCE, remote))
invoker.tasksSetStar(idTask, createStars(local.task));
if(shouldTransmit(local, Task.DUE_DATE, remote))
invoker.tasksSetDeadline(idTask, createDeadline(local.task));
if(shouldTransmit(local, Task.DUE_DATE, remote) && local.task.hasDueDate()) // temporary can't unset deadline
invoker.tasksSetDeadline(idTask, createDeadline(local.task), local.task.hasDueTime() ? 0 : 1);
if(shouldTransmit(local, Task.COMPLETION_DATE, remote))
invoker.tasksSetStatus(idTask, local.task.isCompleted() ? 2 : 1);
@ -575,11 +680,8 @@ public class ProducteevSyncProvider extends SyncProvider<ProducteevTaskContainer
*/
private String createDeadline(Task task) {
if(!task.hasDueDate())
return null;
if(!task.hasDueTime())
return ApiUtilities.unixDateToProducteev(task.getValue(Task.DUE_DATE));
String time = ApiUtilities.unixTimeToProducteev(task.getValue(Task.DUE_DATE));
return time.substring(0, time.lastIndexOf(' '));
return "";
return ApiUtilities.unixTimeToProducteev(task.getValue(Task.DUE_DATE));
}
/**

@ -5,7 +5,7 @@ import java.util.Iterator;
import org.json.JSONObject;
import com.todoroo.astrid.api.TaskContainer;
import com.todoroo.astrid.common.TaskContainer;
import com.todoroo.astrid.model.Metadata;
import com.todoroo.astrid.model.Task;

@ -60,7 +60,6 @@ public class NotificationActivity extends TaskListActivity implements OnTimeSetL
@Override
public void onCreate(Bundle savedInstanceState) {
populateFilter(getIntent());
super.onCreate(savedInstanceState);
displayNotificationPopup();

@ -11,6 +11,7 @@ import android.content.res.Resources;
import android.graphics.Color;
import android.media.AudioManager;
import android.net.Uri;
import android.telephony.TelephonyManager;
import android.util.Log;
import com.timsu.astrid.R;
@ -111,7 +112,7 @@ public class Notifications extends BroadcastReceiver {
public boolean showTaskNotification(long id, int type, String reminder) {
Task task;
try {
task = taskDao.fetch(id, Task.TITLE, Task.HIDE_UNTIL, Task.COMPLETION_DATE,
task = taskDao.fetch(id, Task.ID, Task.TITLE, Task.HIDE_UNTIL, Task.COMPLETION_DATE,
Task.DELETION_DATE, Task.REMINDER_FLAGS);
if(task == null)
throw new IllegalArgumentException("cound not find item with id"); //$NON-NLS-1$
@ -121,9 +122,6 @@ public class Notifications extends BroadcastReceiver {
return false;
}
// schedule next notification
ReminderService.getInstance().scheduleAlarm(task);
// you're done - don't sound, do delete
if(task.isCompleted() || task.isDeleted())
return false;
@ -138,7 +136,11 @@ public class Notifications extends BroadcastReceiver {
// update last reminder time
task.setValue(Task.REMINDER_LAST, DateUtilities.now());
taskDao.saveExisting(task);
boolean saved = taskDao.saveExisting(task);
// schedule next notification (unless couldn't save last time)
if(saved)
ReminderService.getInstance().scheduleAlarm(task);
Context context = ContextManager.getContext();
String title = context.getString(R.string.app_name);
@ -146,7 +148,7 @@ public class Notifications extends BroadcastReceiver {
Intent notifyIntent = new Intent(context, NotificationActivity.class);
notifyIntent.putExtra(NotificationActivity.TOKEN_ID, id);
notifyIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
showNotification((int)id, notifyIntent, type, title, text, nonstopMode);
return true;
@ -215,6 +217,10 @@ public class Notifications extends BroadcastReceiver {
AudioManager audioManager = (AudioManager)context.getSystemService(
Context.AUDIO_SERVICE);
// detect call state
TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
int callState = tm.getCallState();
// if non-stop mode is activated, set up the flags for insistent
// notification, and increase the volume to full volume, so the user
// will actually pay attention to the alarm
@ -228,7 +234,7 @@ public class Notifications extends BroadcastReceiver {
}
// quiet hours = no sound
if(quietHours) {
if(quietHours || callState != TelephonyManager.CALL_STATE_IDLE) {
notification.sound = null;
} else {
String notificationPreference = Preferences.getStringValue(R.string.p_rmd_ringtone);
@ -247,8 +253,9 @@ public class Notifications extends BroadcastReceiver {
}
// quiet hours && ! due date or snooze = no vibrate
if(quietHours && !(type == ReminderService.TYPE_DUE ||
type == ReminderService.TYPE_SNOOZE)) {
if(quietHours && !(type == ReminderService.TYPE_DUE || type == ReminderService.TYPE_SNOOZE)) {
notification.vibrate = null;
} else if(callState != TelephonyManager.CALL_STATE_IDLE) {
notification.vibrate = null;
} else {
if (Preferences.getBoolean(R.string.p_rmd_vibrate, true)

@ -39,6 +39,7 @@ public final class ReminderService {
private static final Property<?>[] PROPERTIES = new Property<?>[] {
Task.ID,
Task.CREATION_DATE,
Task.COMPLETION_DATE,
Task.DELETION_DATE,
Task.DUE_DATE,
@ -186,8 +187,11 @@ public final class ReminderService {
}
/**
* Calculate the next alarm time for overdue reminders. If the due date
* has passed, we schedule a reminder some time in the next 4 - 24 hours.
* Calculate the next alarm time for overdue reminders.
* <p>
* We schedule an alarm for after the due date (which could be in the past),
* with the exception that if a reminder was recently issued, we move
* the alarm time to the near future.
*
* @param task
* @return
@ -195,17 +199,31 @@ public final class ReminderService {
private long calculateNextOverdueReminder(Task task) {
if(task.hasDueDate() && task.getFlag(Task.REMINDER_FLAGS, Task.NOTIFY_AFTER_DEADLINE)) {
long dueDate = task.getValue(Task.DUE_DATE);
if(dueDate < DateUtilities.now())
dueDate = DateUtilities.now();
return dueDate + (long)((4 + 30 * random.nextFloat()) * DateUtilities.ONE_HOUR);
long lastReminder = task.getValue(Task.REMINDER_LAST);
if(dueDate > DateUtilities.now())
return dueDate + (long)((0.5f + 2f * random.nextFloat()) * DateUtilities.ONE_HOUR);
if(lastReminder < dueDate)
return DateUtilities.now();
if(DateUtilities.now() - lastReminder < 6 * DateUtilities.ONE_HOUR)
return DateUtilities.now() + (long)((1.0f + 3f * random.nextFloat()) * DateUtilities.ONE_HOUR);
return DateUtilities.now();
}
return NO_ALARM;
}
/**
* Calculate the next alarm time for due date reminders. If the due date
* has not already passed, we return the due date, altering the time
* if the date was indicated to not have a due time
* Calculate the next alarm time for due date reminders.
* <p>
* This alarm always returns the due date, and is triggered if
* the last reminder time occurred before the due date. This means it is
* possible to return due dates in the past.
* <p>
* If the date was indicated to not have a due time, we read from
* preferences and assign a time.
*
* @param task
* @return
@ -213,26 +231,38 @@ public final class ReminderService {
private long calculateNextDueDateReminder(Task task) {
if(task.hasDueDate() && task.getFlag(Task.REMINDER_FLAGS, Task.NOTIFY_AT_DEADLINE)) {
long dueDate = task.getValue(Task.DUE_DATE);
if(dueDate < DateUtilities.now())
return NO_ALARM;
else if(task.hasDueTime())
long lastReminder = task.getValue(Task.REMINDER_LAST);;
long dueDateAlarm;
if(task.hasDueTime())
// return due date straight up
return dueDate;
dueDateAlarm = dueDate;
else {
// return notification time on this day
Date date = new Date(dueDate);
date.setHours(Preferences.getIntegerFromString(R.string.p_rmd_time, 12));
date.setMinutes(0);
return date.getTime();
dueDateAlarm = date.getTime();
}
if(lastReminder > dueDateAlarm)
return NO_ALARM;
if(dueDate > DateUtilities.now() && dueDateAlarm < DateUtilities.now())
dueDateAlarm = dueDate;
return dueDateAlarm;
}
return NO_ALARM;
}
/**
* Calculate the next alarm time for random reminders. We take the last
* random reminder time and add approximately the reminder period, until
* we get a time that's in the future.
* Calculate the next alarm time for random reminders.
* <p>
* We take the last reminder time and add approximately the reminder
* period. If it's still in the past, we set it to some time in the near
* future.
*
* @param task
* @return
@ -242,14 +272,16 @@ public final class ReminderService {
if((reminderPeriod) > 0) {
long when = task.getValue(Task.REMINDER_LAST);
// get or make up a last notification time
if(when == 0) {
when = DateUtilities.now();
task.setValue(Task.REMINDER_LAST, when);
taskDao.saveExisting(task);
}
if(when == 0)
when = task.getValue(Task.CREATION_DATE);
when += (long)(reminderPeriod * (0.85f + 0.3f * random.nextFloat()));
if(when < DateUtilities.now()) {
when = DateUtilities.now() + (long)((0.5f +
6 * random.nextFloat()) * DateUtilities.ONE_HOUR);
}
return when;
}
return NO_ALARM;
@ -309,10 +341,8 @@ public final class ReminderService {
if(time == 0 || time == NO_ALARM)
am.cancel(pendingIntent);
else {
if(time < DateUtilities.now()) {
time = DateUtilities.now() + (long)((0.5f +
4 * random.nextFloat()) * DateUtilities.ONE_HOUR);
}
if(time < DateUtilities.now())
time = DateUtilities.now() + 5000L;
Log.e("Astrid", "Alarm (" + task.getId() + ", " + type +
") set for " + new Date(time));

@ -17,7 +17,6 @@ import com.google.ical.values.RRule;
import com.google.ical.values.WeekdayNum;
import com.timsu.astrid.R;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.DetailExposer;
import com.todoroo.astrid.core.PluginServices;
import com.todoroo.astrid.model.Task;
@ -27,7 +26,7 @@ import com.todoroo.astrid.model.Task;
* @author Tim Su <tim@todoroo.com>
*
*/
public class RepeatDetailExposer extends BroadcastReceiver implements DetailExposer {
public class RepeatDetailExposer extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
@ -119,7 +118,6 @@ public class RepeatDetailExposer extends BroadcastReceiver implements DetailExpo
return null;
}
@Override
public String getPluginIdentifier() {
return RepeatsPlugin.IDENTIFIER;
}

@ -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);
}
}

@ -8,7 +8,6 @@ import android.content.Context;
import android.content.Intent;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.DetailExposer;
/**
* Exposes Task Detail for tags, i.e. "Tags: frogs, animals"
@ -16,7 +15,7 @@ import com.todoroo.astrid.api.DetailExposer;
* @author Tim Su <tim@todoroo.com>
*
*/
public class TagDetailExposer extends BroadcastReceiver implements DetailExposer {
public class TagDetailExposer extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
@ -39,7 +38,6 @@ public class TagDetailExposer extends BroadcastReceiver implements DetailExposer
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
}
@Override
public String getTaskDetails(Context context, long id, boolean extended) {
if(extended)
return null;
@ -51,9 +49,4 @@ public class TagDetailExposer extends BroadcastReceiver implements DetailExposer
return "<img src='silk_tag_pink'/> " + tagList; //$NON-NLS-1$
}
@Override
public String getPluginIdentifier() {
return TagsPlugin.IDENTIFIER;
}
}

@ -11,6 +11,7 @@ import android.content.Intent;
import android.graphics.Color;
import android.os.SystemClock;
import android.text.format.DateUtils;
import android.view.View;
import android.widget.RemoteViews;
import com.timsu.astrid.R;
@ -33,8 +34,12 @@ public class TimerDecorationExposer extends BroadcastReceiver {
private static HashMap<Long, TaskDecoration> decorations =
new HashMap<Long, TaskDecoration>();
public static void removeFromCache(long taskId) {
private static HashMap<Long, Boolean> tasksNeedingUpdate =
new HashMap<Long, Boolean>();
public static void updateTimer(long taskId) {
decorations.remove(taskId);
tasksNeedingUpdate.put(taskId, true);
}
@Override
@ -45,11 +50,15 @@ public class TimerDecorationExposer extends BroadcastReceiver {
Task task;
try {
task = PluginServices.getTaskService().fetchById(taskId, Task.ELAPSED_SECONDS, Task.TIMER_START);
if(tasksNeedingUpdate.containsKey(taskId))
task = PluginServices.getTaskService().fetchById(taskId, Task.ID, Task.ELAPSED_SECONDS, Task.TIMER_START);
else
task = intent.getParcelableExtra("model"); //$NON-NLS-1$
} catch (IllegalStateException e) {
return;
}
if(task == null || (task.getValue(Task.ELAPSED_SECONDS) == 0 &&
if(task == null || task.getId() != taskId || (task.getValue(Task.ELAPSED_SECONDS) == 0 &&
task.getValue(Task.TIMER_START) == 0))
return;
@ -70,12 +79,16 @@ public class TimerDecorationExposer extends BroadcastReceiver {
elapsed += DateUtilities.now() - task.getValue(Task.TIMER_START);
decoration.decoration.setChronometer(R.id.timer, SystemClock.elapsedRealtime() -
elapsed, null, true);
decoration.decoration.setViewVisibility(R.id.timer, View.VISIBLE);
decoration.decoration.setViewVisibility(R.id.label, View.GONE);
} else {
// if timer is not started, make the chronometer just a text label,
// since we don't want the time to be displayed relative to elapsed
String format = buildFormat(elapsed);
decoration.decoration.setChronometer(R.id.timer, SystemClock.elapsedRealtime() -
elapsed, format, false);
decoration.color = 0;
decoration.decoration.setTextViewText(R.id.label, format);
decoration.decoration.setViewVisibility(R.id.timer, View.GONE);
decoration.decoration.setViewVisibility(R.id.label, View.VISIBLE);
}

@ -53,7 +53,7 @@ public class TimerPlugin extends BroadcastReceiver {
}
}
PluginServices.getTaskService().save(task);
TimerDecorationExposer.removeFromCache(task.getId());
TimerDecorationExposer.updateTimer(task.getId());
// transmit new intents
Intent intent = new Intent(AstridApiConstants.BROADCAST_REQUEST_ACTIONS);

@ -15,5 +15,5 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="100%p" android:toXDelta="0" android:duration="400"/>
<translate android:fromXDelta="100%p" android:toXDelta="0" android:duration="200"/>
</set>

@ -15,5 +15,5 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="0" android:toXDelta="-100%p" android:duration="400"/>
<translate android:fromXDelta="0" android:toXDelta="-100%p" android:duration="200"/>
</set>

@ -15,5 +15,5 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="-100%p" android:toXDelta="0" android:duration="400"/>
<translate android:fromXDelta="-100%p" android:toXDelta="0" android:duration="200"/>
</set>

@ -15,5 +15,5 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="0" android:toXDelta="100%p" android:duration="400"/>
<translate android:fromXDelta="0" android:toXDelta="100%p" android:duration="200"/>
</set>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 773 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2008 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<transition xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/list_selector_background_pressed" />
<item android:drawable="@drawable/list_selector_background_longpress" />
</transition>

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2008 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_window_focused="false"
android:drawable="@color/task_list_selected" />
<item android:state_focused="true" android:state_pressed="true"
android:drawable="@drawable/list_selector_background_transition" />
<item android:state_focused="false" android:state_pressed="true"
android:drawable="@drawable/list_selector_background_transition" />
<item android:state_focused="true"
android:drawable="@drawable/list_selector_background_focus" />
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 723 B

@ -52,12 +52,6 @@
android:paddingTop="20dip"
android:visibility="gone">
<EditText android:id="@+id/confirmPassword"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:hint="@string/producteev_PLA_confirmPassword"
android:inputType="textPassword"/>
<EditText android:id="@+id/firstName"
android:layout_height="wrap_content"
android:layout_width="fill_parent"

@ -8,13 +8,13 @@
android:paddingBottom="4dip"
android:paddingLeft="4dip"
android:paddingRight="4dip"
android:minHeight="40dip"
android:orientation="vertical">
<LinearLayout android:id="@+id/task_row"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="100"
android:minHeight="40dip"
android:orientation="horizontal">
<!-- importance -->
@ -62,6 +62,7 @@
<TextView android:id="@+id/extendedDetails"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingLeft="4dip"
android:visibility="gone" />
<LinearLayout android:id="@+id/actions"
@ -70,6 +71,19 @@
android:background="#4499bbcc"
android:visibility="gone"
android:paddingTop="4dip"
android:orientation="horizontal" />
android:paddingLeft="4dip"
android:orientation="horizontal">
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
</LinearLayout>

@ -81,8 +81,8 @@
android:background="@android:drawable/divider_horizontal_dark" />
<!-- notes -->
<TextView
android:paddingTop="5dip"
<TextView android:id="@+id/notes_label"
android:paddingTop="5dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/TEA_note_label"

@ -15,6 +15,14 @@
android:src="@drawable/timers_decoration" />
<Chronometer android:id="@+id/timer"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="2"
android:gravity="top"
android:visibility="gone"
android:textSize="10sp" />
<TextView android:id="@+id/label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="2"

@ -6,7 +6,9 @@
<color name="task_list_importance_1">#ffffffff</color>
<color name="task_list_importance_2">#ffcfcfcf</color>
<color name="task_list_importance_3">#ffb8b8b8</color>
<color name="task_list_importance_4">#ffa0a0a0</color>
<color name="task_list_importance_4">#ffa0a0a0</color>
<color name="task_list_selected">#33ffffff</color>
<color name="taskList_dueDateOverdue">#FFFB6666</color>
<color name="taskList_dueDate">#ffF0E89E</color>

@ -56,6 +56,8 @@
<string name="backup_TXI_error">Import Error</string>
<string name="export_toast">Backed Up %s to %s.</string>
<string name="export_toast_no_tasks">No Tasks to Export.</string>
<!-- Progress Dialog Title for exporting -->
<string name="export_progress_title">Exporting...</string>

@ -162,6 +162,9 @@
<!-- Menu: Adjust Sort and Hidden Task Settings -->
<string name="TLA_menu_sort">Sort &amp; Hidden</string>
<!-- Menu: Sync Now -->
<string name="TLA_menu_sync">Sync Now!</string>
<!-- Menu: Settings -->
<string name="TLA_menu_settings">Settings</string>
@ -214,6 +217,9 @@ button: add task & go to the edit page.
<!-- Context Item: undelete task -->
<string name="TAd_contextUndeleteTask">Undelete Task</string>
<!-- Context Item: purge task -->
<string name="TAd_contextPurgeTask">Purge Task</string>
<!-- ============================================== SortSelectionDialog == -->
@ -519,6 +525,9 @@ to the plugin creator for fastest service.
<!-- Sync Notification: toast when sync activated from activity -->
<string name="SyP_progress_toast">Synchronizing...</string>
<!-- Sync Label: used in menu to denote synchronization -->
<string name="SyP_label">Synchronization</string>
<!-- ====================================================== TasksWidget == -->
<!-- Widget text when loading tasks -->

@ -3,8 +3,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<!-- ====================== Plugin Boilerplate ========================= -->
<!-- filters header: Producteev -->
<!-- filters header: Producteev -->
<string name="producteev_FEx_header">Producteev</string>
<!-- filter category for Producteev dashboards -->
@ -15,6 +15,12 @@
<!-- Producteev dashboard filter title (%s => dashboardname) -->
<string name="producteev_FEx_responsible_title">Assigned To \'%s\'</string>
<!-- detail for showing tasks created by someone else (%s => person name) -->
<string name="producteev_PDE_task_from">from %s</string>
<!-- replacement string for task edit "Notes" when using Producteev -->
<string name="producteev_TEA_notes">Add a Comment</string>
<!-- ==================================================== Preferences == -->
@ -27,7 +33,13 @@
<!-- dashboard title for tasks that are not synchronized -->
<string name="producteev_no_dashboard">Do Not Synchronize</string>
<!-- preference title for default dashboard -->
<!-- dashboard spinner entry on TEA for adding a new dashboard -->
<string name="producteev_create_dashboard">Add new Workspace...</string>
<!-- dashboard spinner entry on TEA for adding a new dashboard -->
<string name="producteev_create_dashboard_name">Name for new Workspace</string>
<!-- preference title for default dashboard -->
<string name="producteev_PPr_defaultdash_title">Default Workspace</string>
<!-- preference description for default dashboard (%s -> setting) -->
@ -80,8 +92,11 @@
<!-- ================================================ Synchronization == -->
<!-- title for notification tray when synchronizing -->
<string name="producteev_notification_title">Astrid: Producteev</string>
<!-- title for notification tray after synchronizing -->
<string name="producteev_notification_title">Producteev</string>
<!-- text for notification tray when synchronizing -->
<string name="producteev_notification_text">%s tasks updated / click for more details</string>
<!-- Error msg when io exception -->
<string name="producteev_ioerror">Connection Error! Check your Internet connection.</string>

@ -14,9 +14,10 @@ import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceCategory;
import android.preference.PreferenceScreen;
import android.preference.Preference.OnPreferenceClickListener;
import android.widget.Toast;
import com.timsu.astrid.R;
import com.todoroo.andlib.service.Autowired;
@ -29,6 +30,8 @@ import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.dao.Database;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.producteev.ProducteevPreferences;
import com.todoroo.astrid.rmilk.MilkPreferences;
import com.todoroo.astrid.service.StartupService;
import com.todoroo.astrid.service.TaskService;
import com.todoroo.astrid.utility.Constants;
@ -92,6 +95,12 @@ public class EditPreferences extends TodorooPreferences {
preference.setIntent(intent);
String application = resolveInfo.activityInfo.applicationInfo.loadLabel(pm).toString();
// temporary overrides
if(ProducteevPreferences.class.getName().equals(resolveInfo.activityInfo.name) ||
MilkPreferences.class.getName().equals(resolveInfo.activityInfo.name))
application = getString(R.string.SyP_label);
if(!applicationPreferences.containsKey(application))
applicationPreferences.put(application, new ArrayList<Preference>());
ArrayList<Preference> arrayList = applicationPreferences.get(application);
@ -122,6 +131,18 @@ public class EditPreferences extends TodorooPreferences {
getPreferenceScreen().addPreference(group);
Preference preference = new Preference(this);
preference.setTitle("Flush detail cache");
preference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference p) {
database.openForWriting();
Toast.makeText(EditPreferences.this, "" + taskService.clearDetails(),
Toast.LENGTH_LONG).show();
return false;
}
});
group.addPreference(preference);
preference = new Preference(this);
preference.setTitle("Make Lots of Tasks");
preference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference p) {

@ -99,13 +99,14 @@ public class SortSelectionActivity {
break;
case SortSelectionActivity.SORT_DUE:
order = Order.asc(Functions.caseStatement(Task.DUE_DATE.eq(0),
DateUtilities.now()*2, Task.DUE_DATE) + "+" + Task.IMPORTANCE);
DateUtilities.now()*2, Task.DUE_DATE) + "+" + Task.IMPORTANCE +
"+3*" + Task.COMPLETION_DATE);
break;
case SortSelectionActivity.SORT_IMPORTANCE:
order = Order.asc(Task.IMPORTANCE + "*" + (2*DateUtilities.now()) + //$NON-NLS-1$
"+" + Functions.caseStatement(Task.DUE_DATE.eq(0), //$NON-NLS-1$
Functions.now() + "+" + DateUtilities.ONE_WEEK, //$NON-NLS-1$
Task.DUE_DATE));
Task.DUE_DATE) + "+8*" + Task.COMPLETION_DATE);
break;
case SortSelectionActivity.SORT_MODIFIED:
order = Order.desc(Task.MODIFICATION_DATE);

@ -58,6 +58,7 @@ import android.widget.LinearLayout;
import android.widget.RemoteViews;
import android.widget.Spinner;
import android.widget.TabHost;
import android.widget.TextView;
import android.widget.TimePicker;
import android.widget.Toast;
import android.widget.ToggleButton;
@ -74,10 +75,10 @@ import com.todoroo.astrid.alarms.AlarmControlSet;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.dao.Database;
import com.todoroo.astrid.gcal.GCalControlSet;
import com.todoroo.astrid.model.AddOn;
import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.producteev.ProducteevControlSet;
import com.todoroo.astrid.producteev.ProducteevUtilities;
import com.todoroo.astrid.reminders.Notifications;
import com.todoroo.astrid.repeats.RepeatControlSet;
import com.todoroo.astrid.service.AddOnService;
import com.todoroo.astrid.service.MetadataService;
@ -250,9 +251,10 @@ public final class TaskEditActivity extends TabActivity {
LinearLayout addonsAddons = (LinearLayout) findViewById(R.id.tab_addons_addons);
try {
AddOn producteevAddon = addOnService.getAddOn(AddOnService.PRODUCTEEV_PACKAGE, "Producteev"); //$NON-NLS-1$
if (addOnService.isInstalled(producteevAddon) && ProducteevUtilities.INSTANCE.isLoggedIn()) {
if(ProducteevUtilities.INSTANCE.isLoggedIn()) {
controls.add(new ProducteevControlSet(this, addonsAddons));
((TextView)findViewById(R.id.notes)).setHint(R.string.producteev_TEA_notes);
((TextView)findViewById(R.id.notes_label)).setHint(R.string.producteev_TEA_notes);
}
} catch (Exception e) {
Log.e("astrid-error", "loading-control-set", e); //$NON-NLS-1$ //$NON-NLS-2$
@ -357,6 +359,9 @@ public final class TaskEditActivity extends TabActivity {
finish();
return;
}
// clear notification
Notifications.cancelNotifications(model.getId());
}
/** Populate UI component values from the model */
@ -790,7 +795,7 @@ public final class TaskEditActivity extends TabActivity {
Task.URGENCY_TOMORROW);
String dayAfterTomorrow = DateUtils.getDayOfWeekString(
new Date(DateUtilities.now() + 2 * DateUtilities.ONE_DAY).getDay() +
Calendar.SUNDAY, 0);
Calendar.SUNDAY, DateUtils.FORMAT_ABBREV_ALL);
urgencyValues[3] = new UrgencyValue(dayAfterTomorrow,
Task.URGENCY_DAY_AFTER);
urgencyValues[4] = new UrgencyValue(labels[4],

@ -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;
@ -77,6 +81,7 @@ import com.todoroo.astrid.reminders.Notifications;
import com.todoroo.astrid.reminders.ReminderService;
import com.todoroo.astrid.reminders.ReminderService.AlarmScheduler;
import com.todoroo.astrid.service.AddOnService;
import com.todoroo.astrid.service.AstridDependencyInjector;
import com.todoroo.astrid.service.MetadataService;
import com.todoroo.astrid.service.StartupService;
import com.todoroo.astrid.service.TaskService;
@ -108,16 +113,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_ADDON_INTENT_ID = Menu.FIRST + 9;
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 + 10;
private static final int CONTEXT_MENU_DEBUG = Menu.FIRST + 30;
// --- constants
@ -147,6 +153,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;
@ -155,11 +162,16 @@ public class TaskListActivity extends ListActivity implements OnScrollListener,
private int sortSort;
private final AtomicReference<String> sqlQueryTemplate = new AtomicReference<String>();
private Timer backgroundTimer;
private final LinkedHashSet<SyncAction> syncActions = new LinkedHashSet<SyncAction>();
/* ======================================================================
* ======================================================= initialization
* ====================================================================== */
static {
AstridDependencyInjector.initialize();
}
public TaskListActivity() {
DependencyInjectionService.getInstance().inject(this);
}
@ -176,13 +188,6 @@ public class TaskListActivity extends ListActivity implements OnScrollListener,
else
setContentView(R.layout.task_list_activity_api3);
Bundle extras = getIntent().getExtras();
if(extras != null && extras.containsKey(TOKEN_FILTER)) {
filter = extras.getParcelable(TOKEN_FILTER);
} else {
filter = CoreFilterExposer.buildInboxFilter(getResources());
}
if(database == null)
return;
@ -196,6 +201,14 @@ public class TaskListActivity extends ListActivity implements OnScrollListener,
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Bundle extras = intent.getExtras();
if(extras != null && extras.containsKey(TOKEN_FILTER)) {
filter = extras.getParcelable(TOKEN_FILTER);
} else {
filter = CoreFilterExposer.buildInboxFilter(getResources());
}
setUpTaskList();
if(Constants.DEBUG)
setTitle("[D] " + filter.title); //$NON-NLS-1$
@ -232,6 +245,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);
@ -266,11 +285,12 @@ public class TaskListActivity extends ListActivity implements OnScrollListener,
Intent intent = new Intent(TaskListActivity.this,
FilterListActivity.class);
startActivity(intent);
if(AndroidUtilities.getSdkVersion() >= 5) {
//overridePendingTransition(R.anim.slide_right_in, R.anim.slide_right_out);
}
}
});
((TextView)findViewById(R.id.listLabel)).setText(filter.title);
// set listener for quick-changing task priority
getListView().setOnKeyListener(new OnKeyListener() {
@Override
@ -357,7 +377,7 @@ public class TaskListActivity extends ListActivity implements OnScrollListener,
}
});
// gestures
// gestures / animation
try {
GestureService.registerGestureDetector(this, R.id.gestures, R.raw.gestures, this);
} catch (VerifyError e) {
@ -418,6 +438,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();
}
@ -438,6 +460,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() {
@ -448,6 +473,29 @@ public class TaskListActivity extends ListActivity implements OnScrollListener,
}
}
/**
* Receiver which receives sync provider intents
*
* @author Tim Su <tim@todoroo.com>
*
*/
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
*
@ -468,9 +516,9 @@ public class TaskListActivity extends ListActivity implements OnScrollListener,
} else if(AstridApiConstants.BROADCAST_SEND_DETAILS.equals(intent.getAction())) {
String detail = extras.getString(AstridApiConstants.EXTRAS_RESPONSE);
if(extras.getBoolean(AstridApiConstants.EXTRAS_EXTENDED))
taskAdapter.detailManager.addNew(taskId, addOn, detail);
else
taskAdapter.extendedDetailManager.addNew(taskId, addOn, detail);
else
taskAdapter.addDetails(taskId, detail);
} else if(AstridApiConstants.BROADCAST_SEND_ACTIONS.equals(intent.getAction())) {
TaskAction action = extras.getParcelable(AstridApiConstants.EXTRAS_RESPONSE);
taskAdapter.taskActionManager.addNew(taskId, addOn, action);
@ -551,6 +599,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);
}
/**
@ -561,6 +614,8 @@ public class TaskListActivity extends ListActivity implements OnScrollListener,
sqlQueryTemplate.set(SortSelectionActivity.adjustQueryForFlagsAndSort(filter.sqlQuery,
sortFlags, sortSort));
((TextView)findViewById(R.id.listLabel)).setText(filter.title);
// perform query
TodorooCursor<Task> currentCursor = taskService.fetchFiltered(
sqlQueryTemplate.get(), null, TaskAdapter.PROPERTIES);
@ -723,6 +778,9 @@ public class TaskListActivity extends ListActivity implements OnScrollListener,
if(task.isDeleted()) {
menu.add(id, CONTEXT_MENU_UNDELETE_TASK_ID, Menu.NONE,
R.string.TAd_contextUndeleteTask);
menu.add(id, CONTEXT_MENU_PURGE_TASK_ID, Menu.NONE,
R.string.TAd_contextPurgeTask);
} else {
menu.add(id, CONTEXT_MENU_EDIT_TASK_ID, Menu.NONE,
R.string.TAd_contextEditTask);
@ -768,6 +826,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<SyncAction> adapter = new ArrayAdapter<SyncAction>(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;
@ -788,6 +884,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$
@ -832,6 +931,13 @@ public class TaskListActivity extends ListActivity implements OnScrollListener,
return true;
}
case CONTEXT_MENU_PURGE_TASK_ID: {
itemId = item.getGroupId();
taskService.purge(itemId);
loadTaskListContent(true);
return true;
}
// --- debug
case CONTEXT_MENU_DEBUG: {
@ -842,6 +948,9 @@ public class TaskListActivity extends ListActivity implements OnScrollListener,
ReminderService.getInstance().setScheduler(new AlarmScheduler() {
@Override
public void createAlarm(Task theTask, long time, int type) {
if(time == 0 || time == Long.MAX_VALUE)
return;
Toast.makeText(TaskListActivity.this, "Scheduled Alarm: " + //$NON-NLS-1$
new Date(time), Toast.LENGTH_LONG).show();
ReminderService.getInstance().setScheduler(null);
@ -872,6 +981,9 @@ public class TaskListActivity extends ListActivity implements OnScrollListener,
Intent intent = new Intent(TaskListActivity.this,
FilterListActivity.class);
startActivity(intent);
if(AndroidUtilities.getSdkVersion() >= 5) {
// overridePendingTransition(R.anim.slide_right_in, R.anim.slide_right_out);
}
}
}
@ -890,4 +1002,4 @@ public class TaskListActivity extends ListActivity implements OnScrollListener,
setUpTaskList();
}
}
}

@ -1,6 +1,7 @@
package com.todoroo.astrid.adapter;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
@ -16,6 +17,7 @@ import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.text.Html;
import android.text.Html.ImageGetter;
import android.text.TextUtils;
import android.text.util.Linkify;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
@ -40,20 +42,17 @@ import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.service.ExceptionService;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.SoftHashMap;
import com.todoroo.astrid.activity.TaskEditActivity;
import com.todoroo.astrid.activity.TaskListActivity;
import com.todoroo.astrid.alarms.AlarmDetailExposer;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.DetailExposer;
import com.todoroo.astrid.api.TaskAction;
import com.todoroo.astrid.api.TaskDecoration;
import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.notes.NoteDetailExposer;
import com.todoroo.astrid.producteev.ProducteevDetailExposer;
import com.todoroo.astrid.repeats.RepeatDetailExposer;
import com.todoroo.astrid.service.AddOnService;
import com.todoroo.astrid.service.TaskService;
import com.todoroo.astrid.tags.TagDetailExposer;
import com.todoroo.astrid.utility.Constants;
import com.todoroo.astrid.utility.Preferences;
@ -71,6 +70,8 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
public static final String DETAIL_SEPARATOR = " | "; //$NON-NLS-1$
public static final String BROADCAST_EXTRA_TASK = "model"; //$NON-NLS-1$
// --- other constants
/** Properties that need to be read from the action item */
@ -82,15 +83,9 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
Task.COMPLETION_DATE,
Task.HIDE_UNTIL,
Task.DELETION_DATE,
};
/** Internal Task Detail exposers */
public static final DetailExposer[] EXPOSERS = new DetailExposer[] {
new TagDetailExposer(),
new RepeatDetailExposer(),
new NoteDetailExposer(),
new ProducteevDetailExposer(),
new AlarmDetailExposer(),
Task.DETAILS,
Task.ELAPSED_SECONDS,
Task.TIMER_START,
};
private static int[] IMPORTANCE_COLORS = null;
@ -103,6 +98,9 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
@Autowired
private TaskService taskService;
@Autowired
private AddOnService addOnService;
protected final ListActivity activity;
protected final HashMap<Long, Boolean> completedItems = new HashMap<Long, Boolean>();
private OnCompletedTaskListener onCompletedTaskListener = null;
@ -110,6 +108,7 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
private final int resource;
private final LayoutInflater inflater;
private int fontSize;
private DetailLoaderThread detailLoader;
private final AtomicReference<String> query;
@ -118,8 +117,7 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
// --- task detail and decoration soft caches
public final DetailManager detailManager = new DetailManager(false);
public final DetailManager extendedDetailManager = new DetailManager(true);
public final DetailManager extendedDetailManager = new DetailManager();
public final DecorationManager decorationManager =
new DecorationManager();
public final TaskActionManager taskActionManager = new TaskActionManager();
@ -157,6 +155,9 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
if(IMPORTANCE_COLORS == null)
IMPORTANCE_COLORS = Task.getImportanceColors(activity.getResources());
}
detailLoader = new DetailLoaderThread();
detailLoader.start();
}
/* ======================================================================
@ -219,6 +220,7 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
ViewHolder viewHolder = ((ViewHolder)view.getTag());
Task task = viewHolder.task;
task.clear();
task.readFromCursor(cursor);
setFieldContentsAndVisibility(view);
@ -269,6 +271,8 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
if(hiddenUntil > DateUtilities.now())
nameValue = r.getString(R.string.TAd_hiddenFormat, nameValue);
nameView.setText(nameValue);
nameView.setMovementMethod(null);
Linkify.addLinks(nameView, Linkify.ALL);
}
@ -322,9 +326,21 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
importanceView.setBackgroundColor(0);
}
String details;
if(taskDetailLoader.containsKey(task.getId()))
details = taskDetailLoader.get(task.getId()).toString();
else
details = task.getValue(Task.DETAILS);
if(TextUtils.isEmpty(details) || DETAIL_SEPARATOR.equals(details)) {
viewHolder.details.setVisibility(View.GONE);
} else {
viewHolder.details.setVisibility(View.VISIBLE);
viewHolder.details.setText(Html.fromHtml(details.trim().replace("\n", //$NON-NLS-1$
"<br>"), detailImageGetter, null)); //$NON-NLS-1$
}
// details and decorations, expanded
if(!isFling) {
detailManager.request(viewHolder);
decorationManager.request(viewHolder);
if(expanded == task.getId()) {
extendedDetailManager.request(viewHolder);
@ -335,8 +351,9 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
}
} else {
long taskId = viewHolder.task.getId();
detailManager.reset(viewHolder, taskId);
decorationManager.reset(viewHolder, taskId);
viewHolder.extendedDetails.setVisibility(View.GONE);
viewHolder.actions.setVisibility(View.GONE);
}
}
@ -346,9 +363,10 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
* created.
*/
private void addListeners(final View container) {
ViewHolder viewHolder = (ViewHolder)container.getTag();
// check box listener
final CheckBox completeBox = ((CheckBox)container.findViewById(R.id.completeBox));
completeBox.setOnClickListener(completeBoxListener);
viewHolder.completeBox.setOnClickListener(completeBoxListener);
// context menu listener
container.setOnCreateContextMenuListener(listener);
@ -357,6 +375,93 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
container.setOnClickListener(listener);
}
/* ======================================================================
* ============================================================== details
* ====================================================================== */
// implementation note: this map is really costly if users have
// a large number of tasks to load, since it all goes into memory.
// it's best to do this, though, in order to append details to each other
private final Map<Long, StringBuilder> taskDetailLoader = Collections.synchronizedMap(new HashMap<Long, StringBuilder>());
private final Task taskDetailContainer = new Task();
public class DetailLoaderThread extends Thread {
@Override
public void run() {
// for all of the tasks returned by our cursor, verify details
TodorooCursor<Task> fetchCursor = taskService.fetchFiltered(
query.get(), null, Task.ID, Task.DETAILS, Task.COMPLETION_DATE);
activity.startManagingCursor(fetchCursor);
try {
Task task = new Task();
for(fetchCursor.moveToFirst(); !fetchCursor.isAfterLast(); fetchCursor.moveToNext()) {
task.clear();
task.readFromCursor(fetchCursor);
if(task.isCompleted())
continue;
if(TextUtils.isEmpty(task.getValue(Task.DETAILS))) {
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_REQUEST_DETAILS);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, task.getId());
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_EXTENDED, false);
activity.sendOrderedBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
taskDetailLoader.put(task.getId(), new StringBuilder());
task.setValue(Task.DETAILS, DETAIL_SEPARATOR);
taskService.save(task);
}
}
} catch (Exception e) {
// suppress silently
}
}
}
/**
* Add detail to a task
*
* @param id
* @param detail
*/
public void addDetails(long id, String detail) {
final StringBuilder details = taskDetailLoader.get(id);
if(details == null)
return;
synchronized(details) {
if(details.toString().contains(detail))
return;
if(details.length() > 0)
details.append(DETAIL_SEPARATOR);
details.append(detail);
taskDetailContainer.setId(id);
taskDetailContainer.setValue(Task.DETAILS, details.toString());
taskService.save(taskDetailContainer);
}
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
ListView listView = activity.getListView();
int scrollPos = listView.getScrollY();
notifyDataSetInvalidated();
listView.scrollTo(0, scrollPos);
}
});
}
private final ImageGetter detailImageGetter = new ImageGetter() {
public Drawable getDrawable(String source) {
Resources r = activity.getResources();
int drawable = r.getIdentifier("drawable/" + source, null, Constants.PACKAGE); //$NON-NLS-1$
if(drawable == 0)
return null;
Drawable d = r.getDrawable(drawable);
d.setBounds(0,0,d.getIntrinsicWidth(),d.getIntrinsicHeight());
return d;
}
};
/* ======================================================================
* ============================================================== add-ons
* ====================================================================== */
@ -365,10 +470,22 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
* Called to tell the cache to be cleared
*/
public void flushCaches() {
detailManager.clearCache();
extendedDetailManager.clearCache();
decorationManager.clearCache();
taskActionManager.clearCache();
taskDetailLoader.clear();
detailLoader = new DetailLoaderThread();
detailLoader.start();
}
/**
* Called to tell the cache to be cleared
*/
public void flushSpecific(long taskId) {
extendedDetailManager.clearCache(taskId);
decorationManager.clearCache(taskId);
taskActionManager.clearCache(taskId);
taskDetailLoader.remove(taskId);
}
/**
@ -378,62 +495,21 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
*/
public class DetailManager extends AddOnManager<String> {
private final ImageGetter imageGetter = new ImageGetter() {
public Drawable getDrawable(String source) {
Resources r = activity.getResources();
int drawable = r.getIdentifier("drawable/" + source, null, Constants.PACKAGE); //$NON-NLS-1$
if(drawable == 0)
return null;
Drawable d = r.getDrawable(drawable);
d.setBounds(0,0,d.getIntrinsicWidth(),d.getIntrinsicHeight());
return d;
}
};
private final boolean extended;
public DetailManager(boolean extended) {
this.extended = extended;
public DetailManager() {
//
}
@Override
Intent createBroadcastIntent(long taskId) {
Intent createBroadcastIntent(Task task) {
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_REQUEST_DETAILS);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_EXTENDED, extended);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, task.getId());
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_EXTENDED, true);
return broadcastIntent;
}
@Override
public boolean request(final ViewHolder viewHolder) {
if(super.request(viewHolder)) {
final long taskId = viewHolder.task.getId();
// load internal details
new Thread() {
@Override
public void run() {
for(DetailExposer exposer : EXPOSERS) {
final String detail = exposer.getTaskDetails(activity,
taskId, extended);
if(detail == null)
continue;
final Collection<String> cacheList =
addIfNotExists(taskId, exposer.getPluginIdentifier(),
detail);
if(cacheList != null) {
if(taskId != viewHolder.task.getId())
continue;
activity.runOnUiThread(new Runnable() {
public void run() {
draw(viewHolder, taskId, cacheList);
}
});
}
}
};
}.start();
return true;
}
return false;
public void addNew(long taskId, String addOn, String item) {
super.addNew(taskId, addOn, item);
}
@SuppressWarnings("nls")
@ -441,9 +517,9 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
void draw(ViewHolder viewHolder, long taskId, Collection<String> details) {
if(details == null || viewHolder.task.getId() != taskId)
return;
TextView view = extended ? viewHolder.extendedDetails : viewHolder.details;
reset(viewHolder, taskId);
if(details.isEmpty() || (extended && expanded != taskId)) {
TextView view = viewHolder.extendedDetails;
if(details.isEmpty() || (expanded != taskId)) {
reset(viewHolder, taskId);
return;
}
view.setVisibility(View.VISIBLE);
@ -455,7 +531,8 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
}
String string = detailText.toString();
if(string.contains("<"))
view.setText(Html.fromHtml(string.trim().replace("\n", "<br>"), imageGetter, null));
view.setText(Html.fromHtml(string.trim().replace("\n", "<br>"),
detailImageGetter, null));
else
view.setText(string.trim());
Linkify.addLinks(view, Linkify.ALL);
@ -463,7 +540,7 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
@Override
void reset(ViewHolder viewHolder, long taskId) {
TextView view = extended ? viewHolder.extendedDetails : viewHolder.details;
TextView view = viewHolder.extendedDetails;
view.setVisibility(View.GONE);
}
}
@ -476,12 +553,18 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
*/
public class DecorationManager extends AddOnManager<TaskDecoration> {
@Override
Intent createBroadcastIntent(long taskId) {
Intent createBroadcastIntent(Task task) {
Intent intent = new Intent(AstridApiConstants.BROADCAST_REQUEST_DECORATIONS);
intent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId);
intent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, task.getId());
intent.putExtra(BROADCAST_EXTRA_TASK, task);
return intent;
}
@Override
public void addNew(long taskId, String addOn, TaskDecoration item) {
super.addNew(taskId, addOn, item);
}
@Override
void draw(ViewHolder viewHolder, long taskId, Collection<TaskDecoration> decorations) {
if(decorations == null || viewHolder.task.getId() != taskId)
@ -491,6 +574,7 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
if(decorations.size() == 0)
return;
int i = 0;
boolean colorSet = false;
viewHolder.decorations = new View[decorations.size()];
@ -520,7 +604,10 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
for(View view : viewHolder.decorations)
viewHolder.taskRow.removeView(view);
}
viewHolder.view.setBackgroundResource(android.R.drawable.list_selector_background);
if(taskId == expanded)
viewHolder.view.setBackgroundResource(R.drawable.list_selector_highlighted);
else
viewHolder.view.setBackgroundResource(android.R.drawable.list_selector_background);
}
}
@ -531,10 +618,15 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
*
*/
public class TaskActionManager extends AddOnManager<TaskAction> {
private final LinearLayout.LayoutParams params =
new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT, 1f);
@Override
Intent createBroadcastIntent(long taskId) {
Intent createBroadcastIntent(Task task) {
Intent intent = new Intent(AstridApiConstants.BROADCAST_REQUEST_ACTIONS);
intent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId);
intent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, task.getId());
return intent;
}
@ -543,15 +635,25 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
if(actions == null || viewHolder.task.getId() != taskId)
return;
reset(viewHolder, taskId);
// hack because we know we have > 1 button
if(addOnService.hasPowerPack() && actions.size() == 0)
return;
for(int i = viewHolder.actions.getChildCount(); i < actions.size() + 1; i++) {
Button editButton = new Button(activity);
editButton.setLayoutParams(params);
viewHolder.actions.addView(editButton);
}
for(int i = actions.size() + 1; i < viewHolder.actions.getChildCount(); i++) {
viewHolder.actions.getChildAt(i).setVisibility(View.GONE);
}
LinearLayout.LayoutParams params =
new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT, 1f);
int i = 0;
Button button = (Button) viewHolder.actions.getChildAt(i++);
Button editButton = new Button(activity);
editButton.setText(R.string.TAd_actionEditTask);
editButton.setOnClickListener(new OnClickListener() {
button.setText(R.string.TAd_actionEditTask);
button.setVisibility(View.VISIBLE);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
Intent intent = new Intent(activity, TaskEditActivity.class);
@ -559,17 +661,15 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
activity.startActivityForResult(intent, TaskListActivity.ACTIVITY_EDIT_TASK);
}
});
editButton.setLayoutParams(params);
viewHolder.actions.addView(editButton);
for(TaskAction action : actions) {
Button view = new Button(activity);
view.setText(action.text);
view.setOnClickListener(new ActionClickListener(action));
view.setLayoutParams(params);
viewHolder.actions.addView(view);
button = (Button) viewHolder.actions.getChildAt(i++);
button.setText(action.text);
button.setVisibility(View.VISIBLE);
button.setOnClickListener(new ActionClickListener(action, viewHolder));
}
reset(viewHolder, taskId);
}
@Override
@ -579,7 +679,6 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
return;
}
viewHolder.actions.setVisibility(View.VISIBLE);
viewHolder.actions.removeAllViews();
}
}
@ -607,19 +706,25 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
};
private final class ActionClickListener implements View.OnClickListener {
TaskAction action;
private final TaskAction action;
private final ViewHolder viewHolder;
public ActionClickListener(TaskAction action) {
public ActionClickListener(TaskAction action, ViewHolder viewHolder) {
this.action = action;
this.viewHolder = viewHolder;
}
public void onClick(View v) {
flushSpecific(viewHolder.task.getId());
try {
action.intent.send();
} catch (Exception e) {
exceptionService.displayAndReportError(activity,
"Error launching action", e); //$NON-NLS-1$
}
decorationManager.request(viewHolder);
extendedDetailManager.request(viewHolder);
taskActionManager.request(viewHolder);
}
};
@ -716,7 +821,7 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
abstract public class AddOnManager<TYPE> {
private final Map<Long, HashMap<String, TYPE>> cache =
new SoftHashMap<Long, HashMap<String, TYPE>>();
new HashMap<Long, HashMap<String, TYPE>>();
// --- interface
@ -735,13 +840,13 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
// request details
draw(viewHolder, taskId, get(taskId));
Intent broadcastIntent = createBroadcastIntent(taskId);
Intent broadcastIntent = createBroadcastIntent(viewHolder.task);
activity.sendOrderedBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
return true;
}
/** creates a broadcast intent for requesting */
abstract Intent createBroadcastIntent(long taskId);
abstract Intent createBroadcastIntent(Task task);
/** updates the given view */
abstract void draw(ViewHolder viewHolder, long taskId, Collection<TYPE> list);
@ -776,6 +881,13 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
cache.clear();
}
/**
* Clears single item from cache
*/
public void clearCache(long taskId) {
cache.remove(taskId);
}
// --- internal goodies
/**

@ -1,26 +0,0 @@
package com.todoroo.astrid.api;
import android.content.Context;
/**
* Internal API for Task Details
*
* @author Tim Su <tim@todoroo.com>
*
*/
public interface DetailExposer {
/**
* @param id
* task id
* @param extended
* whether this request is for extended details (which are
* displayed when user presses a task), or standard (which are
* always displayed)
* @return null if no details, or task details
*/
public String getTaskDetails(Context context, long id, boolean extended);
public String getPluginIdentifier();
}

@ -28,7 +28,7 @@ public class Database extends AbstractDatabase {
* Database version number. This variable must be updated when database
* tables are updated, as it determines whether a database needs updating.
*/
public static final int VERSION = 4;
public static final int VERSION = 5;
/**
* Database name (must be unique)
@ -119,6 +119,10 @@ public class Database extends AbstractDatabase {
append(')');
database.execSQL(sql.toString());
}
case 4: {
database.execSQL("ALTER TABLE " + Task.TABLE.name + " ADD " +
Task.DETAILS.accept(visitor, null));
}
return true;
}

@ -157,6 +157,10 @@ public class TaskDao extends GenericDao<Task> {
return false;
}
// clear task detail cache
if(values != null && !values.containsKey(Task.DETAILS.name))
values.put(Task.DETAILS.name, ""); //$NON-NLS-1$
if (task.getId() == Task.NO_ID) {
saveSuccessful = createNew(task);
} else {
@ -221,10 +225,14 @@ public class TaskDao extends GenericDao<Task> {
* @param skipHooks whether this save occurs as part of a sync
*/
private void afterSave(Task task, ContentValues values) {
if(values != null && values.containsKey(Task.COMPLETION_DATE.name) && task.isCompleted())
afterComplete(task, values);
else
ReminderService.getInstance().scheduleAlarm(task);
if(values != null) {
if(values.containsKey(Task.COMPLETION_DATE.name) && task.isCompleted())
afterComplete(task, values);
else if(values.containsKey(Task.DUE_DATE.name) ||
values.containsKey(Task.REMINDER_FLAGS.name) ||
values.containsKey(Task.REMINDER_PERIOD.name))
ReminderService.getInstance().scheduleAlarm(task);
}
Astrid2TaskProvider.notifyDatabaseModification();
TasksWidget.updateWidgets(ContextManager.getContext());

@ -79,7 +79,13 @@ public final class Task extends AbstractModel {
public static final LongProperty DELETION_DATE = new LongProperty(
TABLE, "deleted");
// --- for migration purposes from astrid 2 (eventually we will want to
/** Cached Details Column - built from add-on detail exposers. A null
* value means there is no value in the cache and it needs to be
* refreshed */
public static final StringProperty DETAILS = new StringProperty(
TABLE, "details");
// --- for migration purposes from astrid 2 (eventually we may want to
// move these into the metadata table and treat them as plug-ins
public static final StringProperty NOTES = new StringProperty(
@ -186,6 +192,7 @@ public final class Task extends AbstractModel {
defaultValues.put(NOTES.name, "");
defaultValues.put(FLAGS.name, 0);
defaultValues.put(TIMER_START.name, 0);
defaultValues.put(DETAILS.name, (String)null);
}
@Override
@ -215,7 +222,7 @@ public final class Task extends AbstractModel {
// --- parcelable helpers
private static final Creator<Task> CREATOR = new ModelCreator<Task>(Task.class);
public static final Creator<Task> CREATOR = new ModelCreator<Task>(Task.class);
@Override
protected Creator<? extends AbstractModel> getCreator() {
@ -398,6 +405,8 @@ public final class Task extends AbstractModel {
* Checks whether this due date has a due time or only a date
*/
public boolean hasDueTime() {
if(!hasDueDate())
return false;
return hasDueTime(getValue(Task.DUE_DATE));
}

@ -42,9 +42,6 @@ public class AddOnService {
/** Astrid Locale package */
public static final String LOCALE_PACKAGE = "com.todoroo.astrid.locale";
/** Astrid Producteev package */
public static final String PRODUCTEEV_PACKAGE = "com.todoroo.astrid.producteev";
/** Astrid Power Pack label */
public static final String POWER_PACK_LABEL = "Astrid Power Pack";
@ -171,7 +168,7 @@ public class AddOnService {
return true;
if(LOCALE_PACKAGE.equals(packageName))
return true;
if(PRODUCTEEV_PACKAGE.equals(packageName))
if(Constants.PACKAGE.equals(packageName))
return true;
Context context = ContextManager.getContext();
@ -234,7 +231,7 @@ public class AddOnService {
list[2] = new AddOn(true, true, "Producteev", null,
"Synchronize with Producteev service. Also changes Astrid's importance levels to stars.",
PRODUCTEEV_PACKAGE, "http://www.producteev.com",
Constants.PACKAGE, "http://www.producteev.com",
((BitmapDrawable)r.getDrawable(R.drawable.icon_producteev)).getBitmap());
list[3] = new AddOn(true, true, "Remember the Milk", null,

@ -1,5 +1,6 @@
package com.todoroo.astrid.service;
import java.io.File;
import java.util.List;
import android.Manifest;
@ -14,16 +15,22 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.util.Log;
import com.flurry.android.FlurryAgent;
import com.timsu.astrid.R;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.service.ExceptionService;
import com.todoroo.andlib.service.ExceptionService.TodorooUncaughtExceptionHandler;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.astrid.alarms.AlarmService;
import com.todoroo.astrid.backup.BackupConstants;
import com.todoroo.astrid.backup.BackupService;
import com.todoroo.astrid.backup.TasksXmlImporter;
import com.todoroo.astrid.dao.Database;
import com.todoroo.astrid.producteev.ProducteevBackgroundService;
import com.todoroo.astrid.producteev.ProducteevUtilities;
import com.todoroo.astrid.reminders.ReminderService;
import com.todoroo.astrid.utility.Constants;
import com.todoroo.astrid.utility.Preferences;
import com.todoroo.astrid.widget.TasksWidget.UpdateService;
@ -55,6 +62,9 @@ public class StartupService {
@Autowired
TaskService taskService;
@Autowired
MetadataService metadataService;
@Autowired
Database database;
@ -89,6 +99,8 @@ public class StartupService {
Log.i("astrid", "Astrid Startup. " + latestSetVersion + //$NON-NLS-1$ //$NON-NLS-2$
" => " + version); //$NON-NLS-1$
databaseRestoreIfEmpty(context);
// invoke upgrade service
boolean justUpgraded = latestSetVersion != version;
if(justUpgraded && version > 0) {
@ -101,6 +113,7 @@ public class StartupService {
// perform startup activities in a background thread
new Thread(new Runnable() {
public void run() {
// start widget updating alarm
AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, UpdateService.class);
@ -111,6 +124,15 @@ public class StartupService {
database.openForWriting();
taskService.cleanup();
// schedule alarms
try {
ReminderService.getInstance().scheduleAllAlarms();
AlarmService.getInstance().scheduleAllAlarms();
} catch (Exception e) {
DependencyInjectionService.getInstance().inject(this);
exceptionService.reportError("reminder-startup", e); //$NON-NLS-1$
}
}
}).start();
@ -129,8 +151,35 @@ public class StartupService {
hasStartedUp = true;
}
/**
* If database exists, no tasks but metadata, and a backup file exists, restore it
*/
private void databaseRestoreIfEmpty(Context context) {
try {
if(Preferences.getCurrentVersion() > 135 && !context.getDatabasePath(database.getName()).exists()) {
// we didn't have a database! restore latest file
File directory = BackupConstants.getExportDirectory();
if(!directory.exists())
return;
File[] children = directory.listFiles();
AndroidUtilities.sortFilesByDateDesc(children);
if(children.length > 0) {
FlurryAgent.onStartSession(context, Constants.FLURRY_KEY);
TasksXmlImporter.importTasks(context, children[0].getAbsolutePath(), null);
FlurryAgent.onEvent("lost-tasks-restored"); //$NON-NLS-1$
}
}
} catch (Exception e) {
Log.w("astrid-database-restore", e); //$NON-NLS-1$
}
}
private static final String P_TASK_KILLER_HELP = "taskkiller"; //$NON-NLS-1$
/**
* Show task killer helper
* @param context
*/
private static void showTaskKillerHelp(final Context context) {
if(!Preferences.getBoolean(P_TASK_KILLER_HELP, false))
return;

@ -1,5 +1,7 @@
package com.todoroo.astrid.service;
import android.content.ContentValues;
import com.todoroo.andlib.data.Property;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.service.Autowired;
@ -132,6 +134,15 @@ public class TaskService {
}
}
/**
* Permanently delete the given task.
*
* @param model
*/
public void purge(long taskId) {
taskDao.delete(taskId);
}
/**
* Clean up tasks. Typically called on startup
*/
@ -196,7 +207,7 @@ public class TaskService {
return Order.asc(Functions.caseStatement(Task.DUE_DATE.eq(0),
DateUtilities.now() + DateUtilities.ONE_WEEK,
Task.DUE_DATE) + " + 200000000 * " +
Task.IMPORTANCE + " + " + Task.COMPLETION_DATE);
Task.IMPORTANCE + " + 2*" + Task.COMPLETION_DATE);
}
/**
@ -212,6 +223,18 @@ public class TaskService {
}
}
/**
* Clear details cache. Useful if user performs some operation that
* affects details
*
* @return # of affected rows
*/
public int clearDetails() {
ContentValues values = new ContentValues();
values.put(Task.DETAILS.name, (String) null);
return taskDao.updateMultiple(values, Criterion.all);
}
/**
* Update database based on selection and values
* @param selection
@ -233,6 +256,21 @@ public class TaskService {
}
}
/**
* Count tasks overall
* @param filter
* @return
*/
public int countTasks() {
TodorooCursor<Task> cursor = query(Query.select(Task.ID));
try {
return cursor.getCount();
} finally {
cursor.close();
}
}
/** count tasks in a given filter */
public int countTasks(Filter filter) {
String queryTemplate = PermaSql.replacePlaceholders(filter.sqlQuery);
TodorooCursor<Task> cursor = query(Query.select(Task.ID).withQueryTemplate(

@ -16,6 +16,8 @@ import com.todoroo.astrid.dao.Database;
public final class UpgradeService {
private static final int V3_2_6 = 153;
private static final int V3_2_5 = 152;
private static final int V3_2_4 = 151;
private static final int V3_2_3 = 150;
private static final int V3_1_0 = 146;
@ -109,11 +111,22 @@ public final class UpgradeService {
"If you liked the old version, you can also go back by " +
"<a href='http://bit.ly/oldastrid'>clicking here</a>",
});
if(from > V2_14_4 && from <= V3_2_4)
if(from > V3_1_0 && from <= V3_2_6)
newVersionString(changeLog, "3.2.7 (8/25/10)", new String[] {
"Fixed: crazy notifications for overdue tasks! :(",
});
if(from > V3_1_0 && from <= V3_2_5)
newVersionString(changeLog, "3.2.7 (8/24/10)", new String[] {
"RTM: fix for login popping up randomly, not syncing priority",
"Sync: added a 'Sync Now!' button to the menu, moved options to Settings",
"Improvements to notification code to remind you of missed notifications",
"Smoother scrolling of task list once details are loaded",
});
if(from > V3_1_0 && from <= V3_2_4)
newVersionString(changeLog, "3.2.5 (8/18/10)", new String[] {
"Fix for duplicated tasks created in RTM",
});
if(from > V2_14_4 && from <= V3_2_3)
if(from > V3_1_0 && from <= V3_2_3)
newVersionString(changeLog, "3.2.5 (8/18/10)", new String[] {
"Fix for duplicated tasks created in Producteev",
"Fix for being able to create tasks without title",

@ -40,7 +40,7 @@ public final class Constants {
/**
* Upgrade time
*/
public static final Date UPGRADE = new Date(110, 8, 1);
public static final Date UPGRADE = new Date(110, 9, 1);
// --- notification id's
@ -53,4 +53,7 @@ public final class Constants {
/** Notification Manager id for locale */
public static final int NOTIFICATION_LOCALE = -3;
/** Notification Manager id for producteev notifications*/
public static final int NOTIFICATION_PRODUCTEEV_NOTIFICATIONS = -4;
}

@ -51,12 +51,13 @@ public class TasksWidget extends AppWidgetProvider {
int[] appWidgetIds) {
try {
ContextManager.setContext(context);
super.onUpdate(context, appWidgetManager, appWidgetIds);
// Start in service to prevent Application Not Responding timeout
updateWidgets(context);
} catch (SecurityException e) {
// :(
} catch (Exception e) {
Log.e("astrid-update-widget", "widget update error", e); //$NON-NLS-1$ //$NON-NLS-2$
}
}
@ -65,7 +66,7 @@ public class TasksWidget extends AppWidgetProvider {
* @param id
*/
public static void updateWidgets(Context context) {
context.startService(new Intent(ContextManager.getContext(),
context.startService(new Intent(context,
TasksWidget.UpdateService.class));
}
@ -146,7 +147,7 @@ public class TasksWidget extends AppWidgetProvider {
filter.sqlQuery, flags, sort) + " LIMIT " + numberOfTasks;
database.openForReading();
cursor = taskService.fetchFiltered(query, null, Task.TITLE, Task.DUE_DATE);
cursor = taskService.fetchFiltered(query, null, Task.ID, Task.TITLE, Task.DUE_DATE, Task.COMPLETION_DATE);
Task task = new Task();
for (int i = 0; i < cursor.getCount() && i < numberOfTasks; i++) {
cursor.moveToPosition(i);
@ -156,7 +157,10 @@ public class TasksWidget extends AppWidgetProvider {
int textColor = Color.WHITE;
textContent = task.getValue(Task.TITLE);
if(task.hasDueDate() && task.getValue(Task.DUE_DATE) < DateUtilities.now())
if(task.isCompleted())
textColor = context.getResources().getColor(R.color.task_list_done);
else if(task.hasDueDate() && task.getValue(Task.DUE_DATE) < DateUtilities.now())
textColor = context.getResources().getColor(R.color.task_list_overdue);
if(i > 0)
@ -178,7 +182,7 @@ public class TasksWidget extends AppWidgetProvider {
}
Intent listIntent = new Intent(context, TaskListActivity.class);
listIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
listIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
if(filter != null) {
listIntent.putExtra(TaskListActivity.TOKEN_FILTER, filter);
listIntent.setType(filter.sqlQuery);
@ -188,7 +192,7 @@ public class TasksWidget extends AppWidgetProvider {
views.setOnClickPendingIntent(R.id.taskbody, pendingIntent);
Intent editIntent = new Intent(context, TaskEditActivity.class);
editIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
editIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
if(filter != null && filter.valuesForNewTasks != null) {
String values = AndroidUtilities.contentValuesToSerializedString(filter.valuesForNewTasks);
editIntent.putExtra(TaskEditActivity.TOKEN_VALUES, values);

Loading…
Cancel
Save