@Override
 protected boolean ok() {
   int deleted = tagService.delete(tag);
   TagData tagData =
       PluginServices.getTagDataService().getTag(tag, TagData.ID, TagData.DELETION_DATE);
   if (tagData != null) {
     tagData.setValue(TagData.DELETION_DATE, DateUtilities.now());
     PluginServices.getTagDataService().save(tagData);
   }
   Toast.makeText(this, getString(R.string.TEA_tags_deleted, tag, deleted), Toast.LENGTH_SHORT)
       .show();
   return true;
 }
  @Override
  public void onReceive(Context context, Intent intent) {
    ContextManager.setContext(context);
    DependencyInjectionService.getInstance().inject(this);
    long taskId = intent.getLongExtra(AstridApiConstants.EXTRAS_TASK_ID, -1);
    if (taskId == -1) return;

    Task task = PluginServices.getTaskService().fetchById(taskId, Task.PROPERTIES);
    if (task == null || !task.isCompleted()) return;

    String recurrence = task.getValue(Task.RECURRENCE);
    if (recurrence != null && recurrence.length() > 0) {
      long newDueDate;
      try {
        newDueDate = computeNextDueDate(task, recurrence);
        if (newDueDate == -1) return;
      } catch (ParseException e) {
        PluginServices.getExceptionService().reportError("repeat-parse", e); // $NON-NLS-1$
        return;
      }

      StatisticsService.reportEvent(StatisticsConstants.V2_TASK_REPEAT);

      long oldDueDate = task.getValue(Task.DUE_DATE);
      long repeatUntil = task.getValue(Task.REPEAT_UNTIL);

      boolean repeatFinished = repeatUntil > 0 && newDueDate >= repeatUntil;
      if (repeatFinished) {
        Intent repeatFinishedIntent =
            new Intent(AstridApiConstants.BROADCAST_EVENT_TASK_REPEAT_FINISHED);
        repeatFinishedIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, task.getId());
        repeatFinishedIntent.putExtra(AstridApiConstants.EXTRAS_OLD_DUE_DATE, oldDueDate);
        repeatFinishedIntent.putExtra(AstridApiConstants.EXTRAS_NEW_DUE_DATE, newDueDate);
        context.sendOrderedBroadcast(repeatFinishedIntent, null);
        return;
      }

      rescheduleTask(task, newDueDate);

      // send a broadcast
      Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_EVENT_TASK_REPEATED);
      broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, task.getId());
      broadcastIntent.putExtra(AstridApiConstants.EXTRAS_OLD_DUE_DATE, oldDueDate);
      broadcastIntent.putExtra(AstridApiConstants.EXTRAS_NEW_DUE_DATE, newDueDate);
      context.sendOrderedBroadcast(broadcastIntent, null);
      Flags.set(Flags.REFRESH);
      return;
    }
  }
Exemple #3
0
 public static ActFmSyncThread getInstance() {
   if (instance == null) {
     synchronized (ActFmSyncThread.class) {
       if (instance == null) {
         initializeSyncComponents(
             PluginServices.getTaskDao(),
             PluginServices.getTagDataDao(),
             PluginServices.getUserActivityDao(),
             PluginServices.getTaskAttachmentDao(),
             PluginServices.getTaskListMetadataDao());
       }
     }
   }
   return instance;
 }
Exemple #4
0
 /**
  * Return alarms for the given task. PLEASE CLOSE THE CURSOR!
  *
  * @param taskId
  */
 public TodorooCursor<Metadata> getAlarms(long taskId) {
   return PluginServices.getMetadataService()
       .query(
           Query.select(Metadata.PROPERTIES)
               .where(MetadataCriteria.byTaskAndwithKey(taskId, AlarmFields.METADATA_KEY))
               .orderBy(Order.asc(AlarmFields.TIME)));
 }
  private void write(GtasksTaskContainer task) throws IOException {
    //  merge astrid dates with google dates
    if (!task.task.isSaved() && actFmPreferenceService.isLoggedIn()) titleMatchWithActFm(task.task);

    if (task.task.isSaved()) {
      Task local =
          PluginServices.getTaskService()
              .fetchById(task.task.getId(), Task.DUE_DATE, Task.COMPLETION_DATE);
      if (local == null) {
        task.task.clearValue(Task.ID);
      } else {
        mergeDates(task.task, local);
        if (task.task.isCompleted() && !local.isCompleted())
          StatisticsService.reportEvent(StatisticsConstants.GTASKS_TASK_COMPLETED);
      }
    } else { // Set default importance and reminders for remotely created tasks
      task.task.setValue(
          Task.IMPORTANCE,
          Preferences.getIntegerFromString(
              R.string.p_default_importance_key, Task.IMPORTANCE_SHOULD_DO));
      TaskDao.setDefaultReminders(task.task);
    }
    if (!TextUtils.isEmpty(task.task.getValue(Task.TITLE))) {
      task.task.putTransitory(SyncFlags.GTASKS_SUPPRESS_SYNC, true);
      gtasksMetadataService.saveTaskAndMetadata(task);
    }
  }
  private Filter[] buildSavedFilters(Context context, Resources r) {
    int themeFlags = ThemeService.getFilterThemeFlags();

    StoreObjectDao dao = PluginServices.getStoreObjectDao();
    TodorooCursor<StoreObject> cursor =
        dao.query(
            Query.select(StoreObject.PROPERTIES)
                .where(StoreObject.TYPE.eq(SavedFilter.TYPE))
                .orderBy(Order.asc(SavedFilter.NAME)));
    try {
      ArrayList<Filter> list = new ArrayList<>();

      // stock filters
      if (Preferences.getBoolean(R.string.p_show_recently_modified_filter, true)) {
        Filter recent =
            new Filter(
                r.getString(R.string.BFE_Recent),
                r.getString(R.string.BFE_Recent),
                new QueryTemplate()
                    .where(TaskCriteria.ownedByMe())
                    .orderBy(Order.desc(Task.MODIFICATION_DATE))
                    .limit(15),
                null);
        recent.listingIcon =
            ((BitmapDrawable)
                    r.getDrawable(ThemeService.getDrawable(R.drawable.filter_pencil, themeFlags)))
                .getBitmap();

        list.add(recent);
      }

      if (cursor != null) {
        StoreObject savedFilter = new StoreObject();
        for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
          savedFilter.readFromCursor(cursor);
          Filter f = SavedFilter.load(savedFilter);

          Intent deleteIntent = new Intent(context, DeleteActivity.class);
          deleteIntent.putExtra(TOKEN_FILTER_ID, savedFilter.getId());
          deleteIntent.putExtra(TOKEN_FILTER_NAME, f.title);
          f.contextMenuLabels = new String[] {context.getString(R.string.BFE_Saved_delete)};
          f.contextMenuIntents = new Intent[] {deleteIntent};
          f.listingIcon =
              ((BitmapDrawable)
                      r.getDrawable(
                          ThemeService.getDrawable(R.drawable.filter_sliders, themeFlags)))
                  .getBitmap();
          list.add(f);
        }
      }

      return list.toArray(new Filter[list.size()]);
    } finally {
      if (cursor != null) {
        cursor.close();
      }
    }
  }
 public static boolean getOutstandingEntryFlag() {
   if (outstandingEntryFlag == -1) {
     synchronized (RemoteModelDao.class) {
       if (PluginServices.getActFmPreferenceService().isLoggedIn()) outstandingEntryFlag = 1;
       else outstandingEntryFlag = 0;
     }
   }
   return outstandingEntryFlag > 0;
 }
Exemple #8
0
 /**
  * Gets a listing of all alarms that are active
  *
  * @param properties
  * @return todoroo cursor. PLEASE CLOSE THIS CURSOR!
  */
 private TodorooCursor<Metadata> getActiveAlarms() {
   return PluginServices.getMetadataService()
       .query(
           Query.select(AlarmFields.TIME)
               .join(Join.inner(Task.TABLE, Metadata.TASK.eq(Task.ID)))
               .where(
                   Criterion.and(
                       TaskCriteria.isActive(),
                       MetadataCriteria.withKey(AlarmFields.METADATA_KEY))));
 }
 private static void setShowFriendsView() {
   // Show friends view if necessary
   boolean showFriends = false;
   TodorooCursor<User> users = PluginServices.getUserDao().query(Query.select(User.ID).limit(1));
   try {
     showFriends = users.getCount() > 0;
   } finally {
     users.close();
   }
   Preferences.setBoolean(R.string.p_show_friends_view, showFriends);
 }
Exemple #10
0
 private void fetchTask(long id) {
   task =
       PluginServices.getTaskService()
           .fetchById(
               id,
               Task.NOTES,
               Task.ID,
               Task.UUID,
               Task.TITLE,
               Task.HISTORY_FETCH_DATE,
               Task.HISTORY_HAS_MORE,
               Task.USER_ACTIVITIES_PUSHED_AT,
               Task.ATTACHMENTS_PUSHED_AT);
 }
Exemple #11
0
 @Override
 public void onReceive(Context context, Intent intent) {
   lastSyncFromNetworkChange = Preferences.getLong(PREF_LAST_SYNC_FROM_NETWORK_CHANGE, 0L);
   if (DateUtilities.now() - lastSyncFromNetworkChange > DateUtilities.ONE_MINUTE * 10) {
     NetworkInfo info = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
     if (info != null
         && NetworkInfo.State.CONNECTED.equals(info.getState())
         && PluginServices.getActFmPreferenceService().isLoggedIn()) {
       ActFmSyncThread syncThread = ActFmSyncThread.getInstance();
       syncThread.repopulateQueueFromOutstandingTables();
       Preferences.setLong(PREF_LAST_SYNC_FROM_NETWORK_CHANGE, DateUtilities.now());
     }
   }
 }
  public static void rescheduleTask(Task task, long newDueDate) {
    long hideUntil = task.getValue(Task.HIDE_UNTIL);
    if (hideUntil > 0 && task.getValue(Task.DUE_DATE) > 0) {
      hideUntil += newDueDate - task.getValue(Task.DUE_DATE);
    }

    task.setValue(Task.COMPLETION_DATE, 0L);
    task.setValue(Task.DUE_DATE, newDueDate);
    task.setValue(Task.HIDE_UNTIL, hideUntil);
    task.putTransitory(TaskService.TRANS_REPEAT_COMPLETE, true);

    ContentResolver cr = ContextManager.getContext().getContentResolver();
    GCalHelper.rescheduleRepeatingTask(task, cr);
    PluginServices.getTaskService().save(task);
  }
 private static void setShowFeaturedLists() {
   // Show featured lists if necessary
   boolean showFeaturedLists = false;
   TodorooCursor<TagData> featLists =
       PluginServices.getTagDataService()
           .query(
               Query.select(TagData.ID)
                   .where(Functions.bitwiseAnd(TagData.FLAGS, TagData.FLAG_FEATURED).gt(0))
                   .limit(1));
   try {
     showFeaturedLists = featLists.getCount() > 0;
   } finally {
     featLists.close();
   }
   Preferences.setBoolean(
       FeaturedListFilterExposer.PREF_SHOULD_SHOW_FEATURED_LISTS, showFeaturedLists);
 }
Exemple #14
0
  /**
   * Save the given array of alarms into the database
   *
   * @param taskId
   * @param tags
   * @return true if data was changed
   */
  public boolean synchronizeAlarms(long taskId, LinkedHashSet<Long> alarms) {
    MetadataService service = PluginServices.getMetadataService();

    ArrayList<Metadata> metadata = new ArrayList<Metadata>();
    for (Long alarm : alarms) {
      Metadata item = new Metadata();
      item.setValue(Metadata.KEY, AlarmFields.METADATA_KEY);
      item.setValue(AlarmFields.TIME, alarm);
      item.setValue(AlarmFields.TYPE, AlarmFields.TYPE_SINGLE);
      metadata.add(item);
    }

    boolean changed =
        service.synchronizeMetadata(taskId, metadata, Metadata.KEY.eq(AlarmFields.METADATA_KEY))
            > 0;
    if (changed) scheduleAlarms(taskId);
    return changed;
  }
 private static NowBriefed<?> instantiateNowBriefed(JSONObject json) {
   String table = json.optString("table");
   if (NameMaps.TABLE_ID_TASKS.equals(table))
     return new NowBriefed<Task>(json, PluginServices.getTaskDao());
   else if (NameMaps.TABLE_ID_TAGS.equals(table))
     return new NowBriefed<TagData>(json, PluginServices.getTagDataDao());
   else if (NameMaps.TABLE_ID_USER_ACTIVITY.equals(table))
     return new NowBriefed<UserActivity>(json, PluginServices.getUserActivityDao());
   else if (NameMaps.TABLE_ID_USERS.equals(table))
     return new NowBriefed<User>(json, PluginServices.getUserDao());
   else if (NameMaps.TABLE_ID_ATTACHMENTS.equals(table))
     return new NowBriefed<TaskAttachment>(json, PluginServices.getTaskAttachmentDao());
   else if (NameMaps.TABLE_ID_TASK_LIST_METADATA.equals(table))
     return new NowBriefed<TaskListMetadata>(json, PluginServices.getTaskListMetadataDao());
   else return null;
 }
public class TasksXmlExporter {

  // --- public interface

  /**
   * Import tasks from the given file
   *
   * @param context context
   * @param isService if false, displays ui dialogs
   * @param runAfterExport runnable to run after exporting
   * @param backupDirectoryOverride new backupdirectory, or null to use default
   */
  public static void exportTasks(
      Context context, boolean isService, Runnable runAfterExport, File backupDirectoryOverride) {
    new TasksXmlExporter(context, isService, runAfterExport, backupDirectoryOverride);
  }

  // --- implementation

  private static final int FORMAT = 2;

  private final Context context;
  private int exportCount = 0;
  private XmlSerializer xml;
  private final TaskService taskService = PluginServices.getTaskService();
  private final MetadataService metadataService = PluginServices.getMetadataService();
  private final ExceptionService exceptionService = PluginServices.getExceptionService();

  private final ProgressDialog progressDialog;
  private final Handler handler;
  private final File backupDirectory;

  private void setProgress(final int taskNumber, final int total) {
    handler.post(
        new Runnable() {
          public void run() {
            progressDialog.setMax(total);
            progressDialog.setProgress(taskNumber);
          }
        });
  }

  private TasksXmlExporter(
      final Context context,
      final boolean isService,
      final Runnable runAfterExport,
      File backupDirectoryOverride) {
    this.context = context;
    this.exportCount = 0;
    this.backupDirectory =
        backupDirectoryOverride == null
            ? BackupConstants.defaultExportDirectory()
            : backupDirectoryOverride;

    handler = new Handler();
    progressDialog = new ProgressDialog(context);
    if (!isService) {
      progressDialog.setIcon(android.R.drawable.ic_dialog_info);
      progressDialog.setTitle(R.string.export_progress_title);
      progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
      progressDialog.setProgress(0);
      progressDialog.setCancelable(false);
      progressDialog.setIndeterminate(false);
      progressDialog.show();
      if (context instanceof Activity) progressDialog.setOwnerActivity((Activity) context);
    }

    new Thread(
            new Runnable() {
              @Override
              public void run() {
                try {
                  String output = setupFile(backupDirectory, isService);
                  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);

                  if (!isService) onFinishExport(output);
                } catch (IOException e) {
                  if (!isService)
                    exceptionService.displayAndReportError(
                        context, context.getString(R.string.backup_TXI_error), e);
                  else {
                    exceptionService.reportError("background-backup", e); // $NON-NLS-1$
                    Preferences.setString(BackupPreferences.PREF_BACKUP_LAST_ERROR, e.toString());
                  }
                } finally {
                  if (runAfterExport != null) runAfterExport.run();
                }
              }
            })
        .start();
  }

  @SuppressWarnings("nls")
  private void doTasksExport(String output) throws IOException {
    File xmlFile = new File(output);
    xmlFile.createNewFile();
    FileOutputStream fos = new FileOutputStream(xmlFile);
    xml = Xml.newSerializer();
    xml.setOutput(fos, BackupConstants.XML_ENCODING);

    xml.startDocument(null, null);
    xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);

    xml.startTag(null, BackupConstants.ASTRID_TAG);
    xml.attribute(
        null,
        BackupConstants.ASTRID_ATTR_VERSION,
        Integer.toString(AstridPreferences.getCurrentVersion()));
    xml.attribute(null, BackupConstants.ASTRID_ATTR_FORMAT, Integer.toString(FORMAT));

    serializeTasks();

    xml.endTag(null, BackupConstants.ASTRID_TAG);
    xml.endDocument();
    xml.flush();
    fos.close();
  }

  private void serializeTasks() throws IOException {
    TodorooCursor<Task> cursor =
        taskService.query(Query.select(Task.PROPERTIES).orderBy(Order.asc(Task.ID)));
    try {
      Task task = new Task();
      int length = cursor.getCount();
      for (int i = 0; i < length; i++) {
        cursor.moveToNext();
        task.readFromCursor(cursor);

        setProgress(i, length);

        xml.startTag(null, BackupConstants.TASK_TAG);
        serializeModel(task, Task.PROPERTIES, Task.ID);
        serializeMetadata(task);
        xml.endTag(null, BackupConstants.TASK_TAG);
        this.exportCount++;
      }
    } finally {
      cursor.close();
    }
  }

  private synchronized void serializeMetadata(Task task) throws IOException {
    TodorooCursor<Metadata> cursor =
        metadataService.query(
            Query.select(Metadata.PROPERTIES).where(MetadataCriteria.byTask(task.getId())));
    try {
      Metadata metadata = new Metadata();
      for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
        metadata.readFromCursor(cursor);

        xml.startTag(null, BackupConstants.METADATA_TAG);
        serializeModel(metadata, Metadata.PROPERTIES, Metadata.ID, Metadata.TASK);
        xml.endTag(null, BackupConstants.METADATA_TAG);
      }
    } finally {
      cursor.close();
    }
  }

  /**
   * Turn a model into xml attributes
   *
   * @param model
   */
  private void serializeModel(
      AbstractModel model, Property<?>[] properties, Property<?>... excludes) {
    outer:
    for (Property<?> property : properties) {
      for (Property<?> exclude : excludes) if (property.name.equals(exclude.name)) continue outer;

      try {
        property.accept(xmlWritingVisitor, model);
      } catch (Exception e) {
        Log.e(
            "astrid-exporter", //$NON-NLS-1$
            "Caught exception while reading "
                + property.name
                + //$NON-NLS-1$
                " from "
                + model.getDatabaseValues(),
            e); //$NON-NLS-1$
      }
    }
  }

  private final XmlWritingPropertyVisitor xmlWritingVisitor = new XmlWritingPropertyVisitor();

  private class XmlWritingPropertyVisitor implements PropertyVisitor<Void, AbstractModel> {
    @Override
    public Void visitInteger(Property<Integer> property, AbstractModel data) {
      try {
        xml.attribute(null, property.name, data.getValue(property).toString());
      } catch (UnsupportedOperationException e) {
        // didn't read this value, do nothing
      } catch (IllegalArgumentException e) {
        throw new RuntimeException(e);
      } catch (IllegalStateException e) {
        throw new RuntimeException(e);
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
      return null;
    }

    @Override
    public Void visitLong(Property<Long> property, AbstractModel data) {
      try {
        xml.attribute(null, property.name, data.getValue(property).toString());
      } catch (UnsupportedOperationException e) {
        // didn't read this value, do nothing
      } catch (IllegalArgumentException e) {
        throw new RuntimeException(e);
      } catch (IllegalStateException e) {
        throw new RuntimeException(e);
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
      return null;
    }

    @Override
    public Void visitDouble(Property<Double> property, AbstractModel data) {
      try {
        xml.attribute(null, property.name, data.getValue(property).toString());
      } catch (UnsupportedOperationException e) {
        // didn't read this value, do nothing
      } catch (IllegalArgumentException e) {
        throw new RuntimeException(e);
      } catch (IllegalStateException e) {
        throw new RuntimeException(e);
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
      return null;
    }

    @Override
    public Void visitString(Property<String> property, AbstractModel data) {
      try {
        String value = data.getValue(property);
        if (value == null) return null;
        xml.attribute(null, property.name, value);
      } catch (UnsupportedOperationException e) {
        // didn't read this value, do nothing
      } catch (IllegalArgumentException e) {
        throw new RuntimeException(e);
      } catch (IllegalStateException e) {
        throw new RuntimeException(e);
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
      return null;
    }
  }

  private void onFinishExport(final String outputFile) {
    handler.post(
        new Runnable() {
          @Override
          public void run() {

            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() && context instanceof Activity)
                DialogUtilities.dismissDialog((Activity) context, progressDialog);
            }
          }
        });
  }

  /**
   * Creates directories if necessary and returns fully qualified file
   *
   * @param directory
   * @return output file name
   * @throws IOException
   */
  private String setupFile(File directory, boolean isService) throws IOException {
    File astridDir = directory;
    if (astridDir != null) {
      // Check for /sdcard/astrid directory. If it doesn't exist, make it.
      if (astridDir.exists() || astridDir.mkdir()) {
        String fileName;
        if (isService) {
          fileName = BackupConstants.BACKUP_FILE_NAME;
        } else {
          fileName = BackupConstants.EXPORT_FILE_NAME;
        }
        fileName = String.format(fileName, BackupDateUtilities.getDateForExport());
        return astridDir.getAbsolutePath() + File.separator + fileName;
      } else {
        // Unable to make the /sdcard/astrid directory.
        throw new IOException(
            context.getString(R.string.DLG_error_sdcard, astridDir.getAbsolutePath()));
      }
    } else {
      // Unable to access the sdcard because it's not in the mounted state.
      throw new IOException(context.getString(R.string.DLG_error_sdcard_general));
    }
  }
}
Exemple #17
0
  public void synchronizeMembers(
      TagData tagData, String legacyMembersString, String tagUuid, JSONArray members) {
    long tagId = tagData.getId();
    Set<String> emails = new HashSet<String>();
    Set<String> ids = new HashSet<String>();

    HashMap<String, String> idToEmail = new HashMap<String, String>();

    for (int i = 0; i < members.length(); i++) {
      JSONObject person = members.optJSONObject(i);
      if (person != null) {
        String id = person.optString("id"); // $NON-NLS-1$
        if (!TextUtils.isEmpty(id)) {
          ids.add(id);
        }

        String email = person.optString("email"); // $NON-NLS-1$
        if (!TextUtils.isEmpty(email)) {
          emails.add(email);
        }

        if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(email)) {
          idToEmail.put(id, email);
        }
      }
    }

    if (!TextUtils.isEmpty(legacyMembersString)) {
      try {
        JSONArray legacyMembers = new JSONArray(legacyMembersString);
        for (int i = 0; i < legacyMembers.length(); i++) {
          JSONObject user = legacyMembers.optJSONObject(i);
          if (user != null) {
            String id = user.optString("id"); // $NON-NLS-1$
            String email = user.optString("email"); // $NON-NLS-1$

            if (!TextUtils.isEmpty(id)) {
              createMemberLink(tagId, tagUuid, id, !ids.contains(id), false);
            } else if (!TextUtils.isEmpty(email)) {
              createMemberLink(tagId, tagUuid, email, !emails.contains(email), false);
            }
          }
        }
      } catch (JSONException e) {
        //
      }
      tagData.setValue(TagData.MEMBERS, ""); // $NON-NLS-1$
      PluginServices.getTagDataDao().saveExisting(tagData);
    }

    TodorooCursor<TagMetadata> currentMembers =
        query(
            Query.select(TagMemberMetadata.USER_UUID)
                .where(TagMetadataCriteria.byTagAndWithKey(tagUuid, TagMemberMetadata.KEY)));
    try {
      TagMetadata m = new TagMetadata();
      for (currentMembers.moveToNext();
          !currentMembers.isAfterLast();
          currentMembers.moveToNext()) {
        m.clear();
        m.readFromCursor(currentMembers);

        String userId = m.getValue(TagMemberMetadata.USER_UUID);
        boolean exists = ids.remove(userId) || emails.remove(userId);
        if (exists && idToEmail.containsKey(userId)) {
          String email = idToEmail.get(userId);
          emails.remove(email);
        }

        if (!exists) { // Was in database, but not in new members list
          removeMemberLink(tagId, tagUuid, userId, false);
        }
      }
    } finally {
      currentMembers.close();
    }

    for (String email : emails) {
      createMemberLink(tagId, tagUuid, email, false);
    }

    for (String id : ids) {
      createMemberLink(tagId, tagUuid, id, false);
    }
  }
  private Filter[] buildSavedFilters(Context context, Resources r) {
    int themeFlags = ThemeService.getFilterThemeFlags();

    boolean useCustomFilters = Preferences.getBoolean(R.string.p_use_filters, true);
    StoreObjectDao dao = PluginServices.getStoreObjectDao();
    TodorooCursor<StoreObject> cursor = null;
    if (useCustomFilters)
      cursor =
          dao.query(
              Query.select(StoreObject.PROPERTIES)
                  .where(StoreObject.TYPE.eq(SavedFilter.TYPE))
                  .orderBy(Order.asc(SavedFilter.NAME)));
    try {
      Filter[] list;
      if (useCustomFilters && cursor != null) list = new Filter[cursor.getCount() + 3];
      else list = new Filter[3];

      // stock filters
      list[0] = getTodayFilter(r);

      list[1] =
          new Filter(
              r.getString(R.string.BFE_Recent),
              r.getString(R.string.BFE_Recent),
              new QueryTemplate()
                  .where(TaskCriteria.ownedByMe())
                  .orderBy(Order.desc(Task.MODIFICATION_DATE))
                  .limit(15),
              null);
      list[1].listingIcon =
          ((BitmapDrawable)
                  r.getDrawable(ThemeService.getDrawable(R.drawable.filter_pencil, themeFlags)))
              .getBitmap();

      list[2] = getAssignedByMeFilter(r);

      if (useCustomFilters && cursor != null) {
        StoreObject savedFilter = new StoreObject();
        for (int i = 3; i < list.length; i++) {
          cursor.moveToNext();
          savedFilter.readFromCursor(cursor);
          list[i] = SavedFilter.load(savedFilter);

          Intent deleteIntent = new Intent(context, DeleteActivity.class);
          deleteIntent.putExtra(TOKEN_FILTER_ID, savedFilter.getId());
          deleteIntent.putExtra(TOKEN_FILTER_NAME, list[i].title);
          list[i].contextMenuLabels = new String[] {context.getString(R.string.BFE_Saved_delete)};
          list[i].contextMenuIntents = new Intent[] {deleteIntent};
          list[i].listingIcon =
              ((BitmapDrawable)
                      r.getDrawable(
                          ThemeService.getDrawable(R.drawable.filter_sliders, themeFlags)))
                  .getBitmap();
        }
      }

      return list;
    } finally {
      if (cursor != null) cursor.close();
    }
  }