public void testStatsBucketResize() throws Exception {
    NetworkStatsHistory history = null;

    assertStatsFilesExist(false);

    // pretend that wifi network comes online; service should ask about full
    // network state, and poll any existing interfaces before updating.
    expectCurrentTime();
    expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
    expectNetworkState(buildWifiState());
    expectNetworkStatsSummary(buildEmptyStats());
    expectNetworkStatsUidDetail(buildEmptyStats());
    expectNetworkStatsPoll();

    replay();
    mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
    verifyAndReset();

    // modify some number on wifi, and trigger poll event
    incrementCurrentTime(2 * HOUR_IN_MILLIS);
    expectCurrentTime();
    expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
    expectNetworkStatsSummary(
        new NetworkStats(getElapsedRealtime(), 1).addIfaceValues(TEST_IFACE, 512L, 4L, 512L, 4L));
    expectNetworkStatsUidDetail(buildEmptyStats());
    expectNetworkStatsPoll();

    replay();
    mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));

    // verify service recorded history
    history = mService.getHistoryForNetwork(sTemplateWifi, FIELD_ALL);
    assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, 512L, 4L, 512L, 4L, 0);
    assertEquals(HOUR_IN_MILLIS, history.getBucketDuration());
    assertEquals(2, history.size());
    verifyAndReset();

    // now change bucket duration setting and trigger another poll with
    // exact same values, which should resize existing buckets.
    expectCurrentTime();
    expectSettings(0L, 30 * MINUTE_IN_MILLIS, WEEK_IN_MILLIS);
    expectNetworkStatsSummary(buildEmptyStats());
    expectNetworkStatsUidDetail(buildEmptyStats());
    expectNetworkStatsPoll();

    replay();
    mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));

    // verify identical stats, but spread across 4 buckets now
    history = mService.getHistoryForNetwork(sTemplateWifi, FIELD_ALL);
    assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, 512L, 4L, 512L, 4L, 0);
    assertEquals(30 * MINUTE_IN_MILLIS, history.getBucketDuration());
    assertEquals(4, history.size());
    verifyAndReset();
  }
  /**
   * Erase any existing {@link Path} and generate series outline based on currently bound {@link
   * NetworkStatsHistory} data.
   */
  private void generatePath() {
    if (LOGD) Log.d(TAG, "generatePath()");

    mMax = 0;
    mPathStroke.reset();
    mPathFill.reset();
    mPathEstimate.reset();
    mPathValid = true;

    // bail when not enough stats to render
    if (mStats == null || mStats.size() < 2) {
      return;
    }

    final int width = getWidth();
    final int height = getHeight();

    boolean started = false;
    float lastX = 0;
    float lastY = height;
    long lastTime = mHoriz.convertToValue(lastX);

    // move into starting position
    mPathStroke.moveTo(lastX, lastY);
    mPathFill.moveTo(lastX, lastY);

    // TODO: count fractional data from first bucket crossing start;
    // currently it only accepts first full bucket.

    long totalData = 0;

    NetworkStatsHistory.Entry entry = null;

    final int start = mStats.getIndexBefore(mStart);
    final int end = mStats.getIndexAfter(mEnd);
    for (int i = start; i <= end; i++) {
      entry = mStats.getValues(i, entry);

      final long startTime = entry.bucketStart;
      final long endTime = startTime + entry.bucketDuration;

      final float startX = mHoriz.convertToPoint(startTime);
      final float endX = mHoriz.convertToPoint(endTime);

      // skip until we find first stats on screen
      if (endX < 0) continue;

      // increment by current bucket total
      totalData += entry.rxBytes + entry.txBytes;

      final float startY = lastY;
      final float endY = mVert.convertToPoint(totalData);

      if (lastTime != startTime) {
        // gap in buckets; line to start of current bucket
        mPathStroke.lineTo(startX, startY);
        mPathFill.lineTo(startX, startY);
      }

      // always draw to end of current bucket
      mPathStroke.lineTo(endX, endY);
      mPathFill.lineTo(endX, endY);

      lastX = endX;
      lastY = endY;
      lastTime = endTime;
    }

    // when data falls short, extend to requested end time
    if (lastTime < mEndTime) {
      lastX = mHoriz.convertToPoint(mEndTime);

      mPathStroke.lineTo(lastX, lastY);
      mPathFill.lineTo(lastX, lastY);
    }

    if (LOGD) {
      final RectF bounds = new RectF();
      mPathFill.computeBounds(bounds, true);
      Log.d(
          TAG,
          "onLayout() rendered with bounds=" + bounds.toString() + " and totalData=" + totalData);
    }

    // drop to bottom of graph from current location
    mPathFill.lineTo(lastX, height);
    mPathFill.lineTo(0, height);

    mMax = totalData;

    // build estimated data
    mPathEstimate.moveTo(lastX, lastY);

    final long now = System.currentTimeMillis();
    final long bucketDuration = mStats.getBucketDuration();

    // long window is average over two weeks
    entry = mStats.getValues(lastTime - WEEK_IN_MILLIS * 2, lastTime, now, entry);
    final long longWindow = (entry.rxBytes + entry.txBytes) * bucketDuration / entry.bucketDuration;

    long futureTime = 0;
    while (lastX < width) {
      futureTime += bucketDuration;

      // short window is day average last week
      final long lastWeekTime = lastTime - WEEK_IN_MILLIS + (futureTime % WEEK_IN_MILLIS);
      entry = mStats.getValues(lastWeekTime - DAY_IN_MILLIS, lastWeekTime, now, entry);
      final long shortWindow =
          (entry.rxBytes + entry.txBytes) * bucketDuration / entry.bucketDuration;

      totalData += (longWindow * 7 + shortWindow * 3) / 10;

      lastX = mHoriz.convertToPoint(lastTime + futureTime);
      lastY = mVert.convertToPoint(totalData);

      mPathEstimate.lineTo(lastX, lastY);
    }

    mMaxEstimate = totalData;

    invalidate();
  }