public synchronized void updateCategoryTrend(long catId) {
    String trendStr = "trend_unknown";
    float stdDev = 0.0f;
    float lastTrend = 0.0f;
    float newTrend = 0.0f;

    gatherLatestDatapointsLocking(catId, mHistory);
    TimeSeries ts = getSeriesByIdLocking(catId);
    if (ts == null) return;

    if (ts.getDbRow().getSynthetic() == true) return;

    lastTrend = ts.getTrendStats().mTrendPrev;
    newTrend = ts.getTrendStats().mTrend;
    stdDev = ts.getValueStats().mStdDev;

    TrendState state =
        Number.getTrendState(lastTrend, newTrend, ts.getDbRow().getGoal(), mSensitivity, stdDev);
    trendStr = Number.mapTrendStateToString(state);

    mDbh.updateCategoryTrend(catId, trendStr, newTrend);

    if (ts.getDependees() != null && ts.getDependees().size() > 0) {
      for (int i = 0; i < ts.getDependees().size(); i++) {
        TimeSeries dependee = ts.getDependees().get(i);

        for (int j = 0; j < dependee.getDependents().size(); j++) {
          TimeSeries tmp = dependee.getDependents().get(j);
          if (tmp != null) gatherLatestDatapointsLocking(tmp.getDbRow().getId(), mHistory);
        }

        Formula formula = mFormulaCache.getFormula(dependee.getDbRow().getId());
        ArrayList<Datapoint> calculated = formula.apply(dependee.getDependents());
        dependee.setDatapoints(null, calculated, null, true);

        lastTrend = dependee.getTrendStats().mTrendPrev;
        newTrend = dependee.getTrendStats().mTrend;
        stdDev = dependee.getValueStats().mStdDev;

        state =
            Number.getTrendState(
                lastTrend, newTrend, dependee.getDbRow().getGoal(), mSensitivity, stdDev);
        trendStr = Number.mapTrendStateToString(state);

        mDbh.updateCategoryTrend(dependee.getDbRow().getId(), trendStr, newTrend);
      }
    }
  }
  public synchronized void gatherLatestDatapointsLocking(long catId, int history) {
    waitForLock();
    mDatapointCache.populateLatest(catId, history);
    TimeSeries ts = getSeriesByIdNonlocking(catId);
    if (ts == null) {
      unlock();
      return;
    }

    if (ts.getDbRow().getSynthetic() == false) {
      ts.clearSeries();

      EntryDbTable.Row entry = mDbh.fetchLastCategoryEntry(catId);
      if (entry != null) {
        ArrayList<Datapoint> l = mDatapointCache.getLast(catId, history);
        if (l == null
            || l.size() < 1
            || entry.getTimestamp() > l.get(0).mMillis
            || entry.getValue() != l.get(0).mValue.y) {
          mDatapointCache.clearCache(catId);
          mDatapointCache.populateLatest(catId, history);
          l = mDatapointCache.getLast(catId, history);
        }

        l = aggregateDatapoints(l, ts.getDbRow().getType());
        ts.setDatapoints(null, l, null, true);
      }
    }

    unlock();
  }
  public void updateTimeSeriesMetaLocking(boolean disableByDefault) {
    waitForLock();
    Cursor c = mDbh.fetchAllCategories();
    c.moveToFirst();
    for (int i = 0; i < c.getCount(); i++) {
      CategoryDbTable.Row row = new CategoryDbTable.Row(c);
      updateTimeSeriesMeta(row, disableByDefault);
      c.moveToNext();
    }
    c.close();

    // cycle through again, we may have had a series that was dependent on
    // another series that was created later
    for (int i = 0; i < mSeries.size(); i++) {
      setDependents(mSeries.get(i));
      setDependees(mSeries.get(i));
    }

    unlock();
  }