Merge "NumberPicker should not throw an exception if no enough values for wrapping."
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index e8f60b4..7a1ef66 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -102,6 +102,15 @@
this.operations = operations;
}
+ public boolean isNegative() {
+ return rxBytes < 0 || rxPackets < 0 || txBytes < 0 || txPackets < 0 || operations < 0;
+ }
+
+ public boolean isEmpty() {
+ return rxBytes == 0 && rxPackets == 0 && txBytes == 0 && txPackets == 0
+ && operations == 0;
+ }
+
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
@@ -343,6 +352,7 @@
* on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface},
* since operation counts are at data layer.
*/
+ @Deprecated
public void spliceOperationsFrom(NetworkStats stats) {
for (int i = 0; i < size; i++) {
final int j = stats.findIndex(IFACE_ALL, uid[i], set[i], tag[i]);
@@ -397,7 +407,7 @@
* Return total of all fields represented by this snapshot object.
*/
public Entry getTotal(Entry recycle) {
- return getTotal(recycle, null, UID_ALL);
+ return getTotal(recycle, null, UID_ALL, false);
}
/**
@@ -405,7 +415,7 @@
* the requested {@link #uid}.
*/
public Entry getTotal(Entry recycle, int limitUid) {
- return getTotal(recycle, null, limitUid);
+ return getTotal(recycle, null, limitUid, false);
}
/**
@@ -413,7 +423,11 @@
* the requested {@link #iface}.
*/
public Entry getTotal(Entry recycle, HashSet<String> limitIface) {
- return getTotal(recycle, limitIface, UID_ALL);
+ return getTotal(recycle, limitIface, UID_ALL, false);
+ }
+
+ public Entry getTotalIncludingTags(Entry recycle) {
+ return getTotal(recycle, null, UID_ALL, true);
}
/**
@@ -423,7 +437,8 @@
* @param limitIface Set of {@link #iface} to include in total; or {@code
* null} to include all ifaces.
*/
- private Entry getTotal(Entry recycle, HashSet<String> limitIface, int limitUid) {
+ private Entry getTotal(
+ Entry recycle, HashSet<String> limitIface, int limitUid, boolean includeTags) {
final Entry entry = recycle != null ? recycle : new Entry();
entry.iface = IFACE_ALL;
@@ -442,7 +457,7 @@
if (matchesUid && matchesIface) {
// skip specific tags, since already counted in TAG_NONE
- if (tag[i] != TAG_NONE) continue;
+ if (tag[i] != TAG_NONE && !includeTags) continue;
entry.rxBytes += rxBytes[i];
entry.rxPackets += rxPackets[i];
@@ -460,7 +475,7 @@
* time, and that none of them have disappeared.
*/
public NetworkStats subtract(NetworkStats right) {
- return subtract(this, right, null);
+ return subtract(this, right, null, null);
}
/**
@@ -471,12 +486,12 @@
* If counters have rolled backwards, they are clamped to {@code 0} and
* reported to the given {@link NonMonotonicObserver}.
*/
- public static NetworkStats subtract(
- NetworkStats left, NetworkStats right, NonMonotonicObserver observer) {
+ public static <C> NetworkStats subtract(
+ NetworkStats left, NetworkStats right, NonMonotonicObserver<C> observer, C cookie) {
long deltaRealtime = left.elapsedRealtime - right.elapsedRealtime;
if (deltaRealtime < 0) {
if (observer != null) {
- observer.foundNonMonotonic(left, -1, right, -1);
+ observer.foundNonMonotonic(left, -1, right, -1, cookie);
}
deltaRealtime = 0;
}
@@ -510,7 +525,7 @@
if (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0
|| entry.txPackets < 0 || entry.operations < 0) {
if (observer != null) {
- observer.foundNonMonotonic(left, i, right, j);
+ observer.foundNonMonotonic(left, i, right, j, cookie);
}
entry.rxBytes = Math.max(entry.rxBytes, 0);
entry.rxPackets = Math.max(entry.rxPackets, 0);
@@ -663,8 +678,8 @@
}
};
- public interface NonMonotonicObserver {
+ public interface NonMonotonicObserver<C> {
public void foundNonMonotonic(
- NetworkStats left, int leftIndex, NetworkStats right, int rightIndex);
+ NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie);
}
}
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
index 8c01331..faf8a3f 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -26,16 +26,18 @@
import static android.net.NetworkStatsHistory.Entry.UNKNOWN;
import static android.net.NetworkStatsHistory.ParcelUtils.readLongArray;
import static android.net.NetworkStatsHistory.ParcelUtils.writeLongArray;
+import static com.android.internal.util.ArrayUtils.total;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.MathUtils;
+import com.android.internal.util.IndentingPrintWriter;
+
import java.io.CharArrayWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
-import java.io.PrintWriter;
import java.net.ProtocolException;
import java.util.Arrays;
import java.util.Random;
@@ -74,6 +76,7 @@
private long[] txBytes;
private long[] txPackets;
private long[] operations;
+ private long totalBytes;
public static class Entry {
public static final long UNKNOWN = -1;
@@ -106,6 +109,12 @@
if ((fields & FIELD_TX_PACKETS) != 0) txPackets = new long[initialSize];
if ((fields & FIELD_OPERATIONS) != 0) operations = new long[initialSize];
bucketCount = 0;
+ totalBytes = 0;
+ }
+
+ public NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration) {
+ this(bucketDuration, existing.estimateResizeBuckets(bucketDuration));
+ recordEntireHistory(existing);
}
public NetworkStatsHistory(Parcel in) {
@@ -118,6 +127,7 @@
txPackets = readLongArray(in);
operations = readLongArray(in);
bucketCount = bucketStart.length;
+ totalBytes = in.readLong();
}
/** {@inheritDoc} */
@@ -130,6 +140,7 @@
writeLongArray(out, txBytes, bucketCount);
writeLongArray(out, txPackets, bucketCount);
writeLongArray(out, operations, bucketCount);
+ out.writeLong(totalBytes);
}
public NetworkStatsHistory(DataInputStream in) throws IOException {
@@ -144,6 +155,7 @@
txPackets = new long[bucketStart.length];
operations = new long[bucketStart.length];
bucketCount = bucketStart.length;
+ totalBytes = total(rxBytes) + total(txBytes);
break;
}
case VERSION_ADD_PACKETS:
@@ -158,6 +170,7 @@
txPackets = readVarLongArray(in);
operations = readVarLongArray(in);
bucketCount = bucketStart.length;
+ totalBytes = total(rxBytes) + total(txBytes);
break;
}
default: {
@@ -208,6 +221,13 @@
}
/**
+ * Return total bytes represented by this history.
+ */
+ public long getTotalBytes() {
+ return totalBytes;
+ }
+
+ /**
* Return index of bucket that contains or is immediately before the
* requested time.
*/
@@ -266,13 +286,16 @@
* distribute across internal buckets, creating new buckets as needed.
*/
public void recordData(long start, long end, NetworkStats.Entry entry) {
- if (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0 || entry.txPackets < 0
- || entry.operations < 0) {
+ long rxBytes = entry.rxBytes;
+ long rxPackets = entry.rxPackets;
+ long txBytes = entry.txBytes;
+ long txPackets = entry.txPackets;
+ long operations = entry.operations;
+
+ if (entry.isNegative()) {
throw new IllegalArgumentException("tried recording negative data");
}
- if (entry.rxBytes == 0 && entry.rxPackets == 0 && entry.txBytes == 0 && entry.txPackets == 0
- && entry.operations == 0) {
- // nothing to record; skip
+ if (entry.isEmpty()) {
return;
}
@@ -295,21 +318,23 @@
if (overlap <= 0) continue;
// integer math each time is faster than floating point
- final long fracRxBytes = entry.rxBytes * overlap / duration;
- final long fracRxPackets = entry.rxPackets * overlap / duration;
- final long fracTxBytes = entry.txBytes * overlap / duration;
- final long fracTxPackets = entry.txPackets * overlap / duration;
- final long fracOperations = entry.operations * overlap / duration;
+ final long fracRxBytes = rxBytes * overlap / duration;
+ final long fracRxPackets = rxPackets * overlap / duration;
+ final long fracTxBytes = txBytes * overlap / duration;
+ final long fracTxPackets = txPackets * overlap / duration;
+ final long fracOperations = operations * overlap / duration;
addLong(activeTime, i, overlap);
- addLong(rxBytes, i, fracRxBytes); entry.rxBytes -= fracRxBytes;
- addLong(rxPackets, i, fracRxPackets); entry.rxPackets -= fracRxPackets;
- addLong(txBytes, i, fracTxBytes); entry.txBytes -= fracTxBytes;
- addLong(txPackets, i, fracTxPackets); entry.txPackets -= fracTxPackets;
- addLong(operations, i, fracOperations); entry.operations -= fracOperations;
+ addLong(this.rxBytes, i, fracRxBytes); rxBytes -= fracRxBytes;
+ addLong(this.rxPackets, i, fracRxPackets); rxPackets -= fracRxPackets;
+ addLong(this.txBytes, i, fracTxBytes); txBytes -= fracTxBytes;
+ addLong(this.txPackets, i, fracTxPackets); txPackets -= fracTxPackets;
+ addLong(this.operations, i, fracOperations); operations -= fracOperations;
duration -= overlap;
}
+
+ totalBytes += entry.rxBytes + entry.txBytes;
}
/**
@@ -394,6 +419,7 @@
/**
* Remove buckets older than requested cutoff.
*/
+ @Deprecated
public void removeBucketsBefore(long cutoff) {
int i;
for (i = 0; i < bucketCount; i++) {
@@ -415,6 +441,8 @@
if (txPackets != null) txPackets = Arrays.copyOfRange(txPackets, i, length);
if (operations != null) operations = Arrays.copyOfRange(operations, i, length);
bucketCount -= i;
+
+ // TODO: subtract removed values from totalBytes
}
}
@@ -527,19 +555,17 @@
return (long) (start + (r.nextFloat() * (end - start)));
}
- public void dump(String prefix, PrintWriter pw, boolean fullHistory) {
- pw.print(prefix);
+ public void dump(IndentingPrintWriter pw, boolean fullHistory) {
pw.print("NetworkStatsHistory: bucketDuration="); pw.println(bucketDuration);
+ pw.increaseIndent();
final int start = fullHistory ? 0 : Math.max(0, bucketCount - 32);
if (start > 0) {
- pw.print(prefix);
- pw.print(" (omitting "); pw.print(start); pw.println(" buckets)");
+ pw.print("(omitting "); pw.print(start); pw.println(" buckets)");
}
for (int i = start; i < bucketCount; i++) {
- pw.print(prefix);
- pw.print(" bucketStart="); pw.print(bucketStart[i]);
+ pw.print("bucketStart="); pw.print(bucketStart[i]);
if (activeTime != null) { pw.print(" activeTime="); pw.print(activeTime[i]); }
if (rxBytes != null) { pw.print(" rxBytes="); pw.print(rxBytes[i]); }
if (rxPackets != null) { pw.print(" rxPackets="); pw.print(rxPackets[i]); }
@@ -548,12 +574,14 @@
if (operations != null) { pw.print(" operations="); pw.print(operations[i]); }
pw.println();
}
+
+ pw.decreaseIndent();
}
@Override
public String toString() {
final CharArrayWriter writer = new CharArrayWriter();
- dump("", new PrintWriter(writer), false);
+ dump(new IndentingPrintWriter(writer, " "), false);
return writer.toString();
}
@@ -579,6 +607,10 @@
if (array != null) array[i] += value;
}
+ public int estimateResizeBuckets(long newBucketDuration) {
+ return (int) (size() * getBucketDuration() / newBucketDuration);
+ }
+
/**
* Utility methods for interacting with {@link DataInputStream} and
* {@link DataOutputStream}, mostly dealing with writing partial arrays.
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index 8bdb669..dfdea38 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -195,7 +195,7 @@
// subtract starting values and return delta
final NetworkStats profilingStop = getDataLayerSnapshotForUid(context);
final NetworkStats profilingDelta = NetworkStats.subtract(
- profilingStop, sActiveProfilingStart, null);
+ profilingStop, sActiveProfilingStart, null, null);
sActiveProfilingStart = null;
return profilingDelta;
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ba9046c..ef8cb16 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4111,17 +4111,38 @@
/** {@hide} */
public static final String NETSTATS_POLL_INTERVAL = "netstats_poll_interval";
/** {@hide} */
- public static final String NETSTATS_PERSIST_THRESHOLD = "netstats_persist_threshold";
+ public static final String NETSTATS_TIME_CACHE_MAX_AGE = "netstats_time_cache_max_age";
/** {@hide} */
- public static final String NETSTATS_NETWORK_BUCKET_DURATION = "netstats_network_bucket_duration";
+ public static final String NETSTATS_GLOBAL_ALERT_BYTES = "netstats_global_alert_bytes";
/** {@hide} */
- public static final String NETSTATS_NETWORK_MAX_HISTORY = "netstats_network_max_history";
+ public static final String NETSTATS_SAMPLE_ENABLED = "netstats_sample_enabled";
+
+ /** {@hide} */
+ public static final String NETSTATS_DEV_BUCKET_DURATION = "netstats_dev_bucket_duration";
+ /** {@hide} */
+ public static final String NETSTATS_DEV_PERSIST_BYTES = "netstats_dev_persist_bytes";
+ /** {@hide} */
+ public static final String NETSTATS_DEV_ROTATE_AGE = "netstats_dev_rotate_age";
+ /** {@hide} */
+ public static final String NETSTATS_DEV_DELETE_AGE = "netstats_dev_delete_age";
+
/** {@hide} */
public static final String NETSTATS_UID_BUCKET_DURATION = "netstats_uid_bucket_duration";
/** {@hide} */
- public static final String NETSTATS_UID_MAX_HISTORY = "netstats_uid_max_history";
+ public static final String NETSTATS_UID_PERSIST_BYTES = "netstats_uid_persist_bytes";
/** {@hide} */
- public static final String NETSTATS_TAG_MAX_HISTORY = "netstats_tag_max_history";
+ public static final String NETSTATS_UID_ROTATE_AGE = "netstats_uid_rotate_age";
+ /** {@hide} */
+ public static final String NETSTATS_UID_DELETE_AGE = "netstats_uid_delete_age";
+
+ /** {@hide} */
+ public static final String NETSTATS_UID_TAG_BUCKET_DURATION = "netstats_uid_tag_bucket_duration";
+ /** {@hide} */
+ public static final String NETSTATS_UID_TAG_PERSIST_BYTES = "netstats_uid_tag_persist_bytes";
+ /** {@hide} */
+ public static final String NETSTATS_UID_TAG_ROTATE_AGE = "netstats_uid_tag_rotate_age";
+ /** {@hide} */
+ public static final String NETSTATS_UID_TAG_DELETE_AGE = "netstats_uid_tag_delete_age";
/** Preferred NTP server. {@hide} */
public static final String NTP_SERVER = "ntp_server";
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index c184c11..a52e2ba 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -109,6 +109,9 @@
for (int i = 0; i < spans.length; i++) {
int startInPara = spanned.getSpanStart(spans[i]) - start;
int endInPara = spanned.getSpanEnd(spans[i]) - start;
+ // The span interval may be larger and must be restricted to [start, end[
+ if (startInPara < 0) startInPara = 0;
+ if (endInPara > len) endInPara = len;
for (int j = startInPara; j < endInPara; j++) {
mChars[j] = '\uFFFC';
}
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index 5ec1ec3..bd02d62 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -193,10 +193,12 @@
/**
* The default implementation performs the deletion around the current
* selection position of the editable text.
+ * @param beforeLength
+ * @param afterLength
*/
- public boolean deleteSurroundingText(int leftLength, int rightLength) {
- if (DEBUG) Log.v(TAG, "deleteSurroundingText " + leftLength
- + " / " + rightLength);
+ public boolean deleteSurroundingText(int beforeLength, int afterLength) {
+ if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength
+ + " / " + afterLength);
final Editable content = getEditable();
if (content == null) return false;
@@ -226,17 +228,17 @@
int deleted = 0;
- if (leftLength > 0) {
- int start = a - leftLength;
+ if (beforeLength > 0) {
+ int start = a - beforeLength;
if (start < 0) start = 0;
content.delete(start, a);
deleted = a - start;
}
- if (rightLength > 0) {
+ if (afterLength > 0) {
b = b - deleted;
- int end = b + rightLength;
+ int end = b + afterLength;
if (end > content.length()) end = content.length();
content.delete(b, end);
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index a6639d1..bc2a270 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -138,19 +138,20 @@
int flags);
/**
- * Delete <var>leftLength</var> characters of text before the current cursor
- * position, and delete <var>rightLength</var> characters of text after the
+ * Delete <var>beforeLength</var> characters of text before the current cursor
+ * position, and delete <var>afterLength</var> characters of text after the
* current cursor position, excluding composing text.
*
- * @param leftLength The number of characters to be deleted before the
+ *
+ * @param beforeLength The number of characters to be deleted before the
* current cursor position.
- * @param rightLength The number of characters to be deleted after the
+ * @param afterLength The number of characters to be deleted after the
* current cursor position.
- *
+ *
* @return Returns true on success, false if the input connection is no longer
* valid.
*/
- public boolean deleteSurroundingText(int leftLength, int rightLength);
+ public boolean deleteSurroundingText(int beforeLength, int afterLength);
/**
* Set composing text around the current cursor position with the given text,
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index 690ea85..a48473e 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -62,8 +62,8 @@
return mTarget.getExtractedText(request, flags);
}
- public boolean deleteSurroundingText(int leftLength, int rightLength) {
- return mTarget.deleteSurroundingText(leftLength, rightLength);
+ public boolean deleteSurroundingText(int beforeLength, int afterLength) {
+ return mTarget.deleteSurroundingText(beforeLength, afterLength);
}
public boolean setComposingText(CharSequence text, int newCursorPosition) {
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 148be5c..3697635 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -384,17 +384,17 @@
}
@Override
- public boolean deleteSurroundingText(int leftLength, int rightLength) {
+ public boolean deleteSurroundingText(int beforeLength, int afterLength) {
// Look for one-character delete and send it as a key press.
- if (leftLength == 1 && rightLength == 0) {
+ if (beforeLength == 1 && afterLength == 0) {
sendKeyPress(KeyEvent.KEYCODE_DEL);
- } else if (leftLength == 0 && rightLength == 1){
+ } else if (beforeLength == 0 && afterLength == 1){
sendKeyPress(KeyEvent.KEYCODE_FORWARD_DEL);
} else if (mWebViewCore != null) {
mWebViewCore.sendMessage(EventHub.DELETE_SURROUNDING_TEXT,
- leftLength, rightLength);
+ beforeLength, afterLength);
}
- return super.deleteSurroundingText(leftLength, rightLength);
+ return super.deleteSurroundingText(beforeLength, afterLength);
}
}
diff --git a/core/java/android/widget/ActivityChooserView.java b/core/java/android/widget/ActivityChooserView.java
index 60b24bc..be6b4e2 100644
--- a/core/java/android/widget/ActivityChooserView.java
+++ b/core/java/android/widget/ActivityChooserView.java
@@ -33,8 +33,6 @@
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
import android.widget.ActivityChooserModel.ActivityChooserModelClient;
/**
@@ -366,7 +364,7 @@
getListPopupWindow().dismiss();
ViewTreeObserver viewTreeObserver = getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
- viewTreeObserver.removeGlobalOnLayoutListener(mOnGlobalLayoutListener);
+ viewTreeObserver.removeOnGlobalLayoutListener(mOnGlobalLayoutListener);
}
}
return true;
@@ -400,7 +398,7 @@
}
ViewTreeObserver viewTreeObserver = getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
- viewTreeObserver.removeGlobalOnLayoutListener(mOnGlobalLayoutListener);
+ viewTreeObserver.removeOnGlobalLayoutListener(mOnGlobalLayoutListener);
}
mIsAttachedToWindow = false;
}
@@ -547,6 +545,7 @@
position = mAdapter.getShowDefaultActivity() ? position : position + 1;
Intent launchIntent = mAdapter.getDataModel().chooseActivity(position);
if (launchIntent != null) {
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
mContext.startActivity(launchIntent);
}
}
@@ -564,6 +563,7 @@
final int index = mAdapter.getDataModel().getActivityIndex(defaultActivity);
Intent launchIntent = mAdapter.getDataModel().chooseActivity(index);
if (launchIntent != null) {
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
mContext.startActivity(launchIntent);
}
} else if (view == mExpandActivityOverflowButton) {
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index e226d37..bb00049 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -558,7 +558,9 @@
mCurrentWindowEnd = newWindowEnd;
mCurrentWindowStartUnbounded = newWindowStartUnbounded;
if (mRemoteViewsAdapter != null) {
- mRemoteViewsAdapter.setVisibleRangeHint(mCurrentWindowStart, mCurrentWindowEnd);
+ int adapterStart = modulo(mCurrentWindowStart, adapterCount);
+ int adapterEnd = modulo(mCurrentWindowEnd, adapterCount);
+ mRemoteViewsAdapter.setVisibleRangeHint(adapterStart, adapterEnd);
}
}
requestLayout();
diff --git a/core/java/android/widget/ShareActionProvider.java b/core/java/android/widget/ShareActionProvider.java
index bb27b73..22e9ef1 100644
--- a/core/java/android/widget/ShareActionProvider.java
+++ b/core/java/android/widget/ShareActionProvider.java
@@ -279,6 +279,7 @@
final int itemId = item.getItemId();
Intent launchIntent = dataModel.chooseActivity(itemId);
if (launchIntent != null) {
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
mContext.startActivity(launchIntent);
}
return true;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 04f4a83..9fb26ae 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -8386,10 +8386,6 @@
}
if (touchIsFinished && (isTextEditable() || mTextIsSelectable)) {
- // Move cursor
- final int offset = getOffsetForPosition(event.getX(), event.getY());
- Selection.setSelection((Spannable) mText, offset);
-
// Show the IME, except when selecting in read-only text.
final InputMethodManager imm = InputMethodManager.peekInstance();
viewClicked(imm);
@@ -8416,6 +8412,9 @@
postDelayed(mShowSuggestionRunnable,
ViewConfiguration.getDoubleTapTimeout());
} else if (hasInsertionController()) {
+ // Move cursor
+ final int offset = getOffsetForPosition(event.getX(), event.getY());
+ Selection.setSelection((Spannable) mText, offset);
getInsertionController().show();
}
}
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index edeb2a8..d1aa1ce 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -142,6 +142,14 @@
return false;
}
+ public static long total(long[] array) {
+ long total = 0;
+ for (long value : array) {
+ total += value;
+ }
+ return total;
+ }
+
/**
* Appends an element to a copy of the array and returns the copy.
* @param array The original array, or null to represent an empty array.
diff --git a/core/java/com/android/internal/util/FileRotator.java b/core/java/com/android/internal/util/FileRotator.java
index 3ce95e7..8a8f315 100644
--- a/core/java/com/android/internal/util/FileRotator.java
+++ b/core/java/com/android/internal/util/FileRotator.java
@@ -17,9 +17,9 @@
package com.android.internal.util;
import android.os.FileUtils;
+import android.util.Slog;
-import com.android.internal.util.FileRotator.Reader;
-import com.android.internal.util.FileRotator.Writer;
+import com.android.internal.util.FileRotator.Rewriter;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
@@ -41,12 +41,15 @@
* Instead of manipulating files directly, users implement interfaces that
* perform operations on {@link InputStream} and {@link OutputStream}. This
* enables atomic rewriting of file contents in
- * {@link #combineActive(Reader, Writer, long)}.
+ * {@link #rewriteActive(Rewriter, long)}.
* <p>
* Users must periodically call {@link #maybeRotate(long)} to perform actual
* rotation. Not inherently thread safe.
*/
public class FileRotator {
+ private static final String TAG = "FileRotator";
+ private static final boolean LOGD = true;
+
private final File mBasePath;
private final String mPrefix;
private final long mRotateAgeMillis;
@@ -73,6 +76,15 @@
}
/**
+ * External class that reads existing data from given {@link InputStream},
+ * then writes any modified data to {@link OutputStream}.
+ */
+ public interface Rewriter extends Reader, Writer {
+ public void reset();
+ public boolean shouldWrite();
+ }
+
+ /**
* Create a file rotator.
*
* @param basePath Directory under which all files will be placed.
@@ -96,6 +108,8 @@
if (!name.startsWith(mPrefix)) continue;
if (name.endsWith(SUFFIX_BACKUP)) {
+ if (LOGD) Slog.d(TAG, "recovering " + name);
+
final File backupFile = new File(mBasePath, name);
final File file = new File(
mBasePath, name.substring(0, name.length() - SUFFIX_BACKUP.length()));
@@ -104,6 +118,8 @@
backupFile.renameTo(file);
} else if (name.endsWith(SUFFIX_NO_BACKUP)) {
+ if (LOGD) Slog.d(TAG, "recovering " + name);
+
final File noBackupFile = new File(mBasePath, name);
final File file = new File(
mBasePath, name.substring(0, name.length() - SUFFIX_NO_BACKUP.length()));
@@ -116,26 +132,95 @@
}
/**
- * Atomically combine data with existing data in currently active file.
- * Maintains a backup during write, which is restored if the write fails.
+ * Delete all files managed by this rotator.
*/
- public void combineActive(Reader reader, Writer writer, long currentTimeMillis)
+ public void deleteAll() {
+ final FileInfo info = new FileInfo(mPrefix);
+ for (String name : mBasePath.list()) {
+ if (!info.parse(name)) continue;
+
+ // delete each file that matches parser
+ new File(mBasePath, name).delete();
+ }
+ }
+
+ /**
+ * Process currently active file, first reading any existing data, then
+ * writing modified data. Maintains a backup during write, which is restored
+ * if the write fails.
+ */
+ public void rewriteActive(Rewriter rewriter, long currentTimeMillis)
throws IOException {
final String activeName = getActiveName(currentTimeMillis);
+ rewriteSingle(rewriter, activeName);
+ }
- final File file = new File(mBasePath, activeName);
+ @Deprecated
+ public void combineActive(final Reader reader, final Writer writer, long currentTimeMillis)
+ throws IOException {
+ rewriteActive(new Rewriter() {
+ /** {@inheritDoc} */
+ public void reset() {
+ // ignored
+ }
+
+ /** {@inheritDoc} */
+ public void read(InputStream in) throws IOException {
+ reader.read(in);
+ }
+
+ /** {@inheritDoc} */
+ public boolean shouldWrite() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ public void write(OutputStream out) throws IOException {
+ writer.write(out);
+ }
+ }, currentTimeMillis);
+ }
+
+ /**
+ * Process all files managed by this rotator, usually to rewrite historical
+ * data. Each file is processed atomically.
+ */
+ public void rewriteAll(Rewriter rewriter) throws IOException {
+ final FileInfo info = new FileInfo(mPrefix);
+ for (String name : mBasePath.list()) {
+ if (!info.parse(name)) continue;
+
+ // process each file that matches parser
+ rewriteSingle(rewriter, name);
+ }
+ }
+
+ /**
+ * Process a single file atomically, first reading any existing data, then
+ * writing modified data. Maintains a backup during write, which is restored
+ * if the write fails.
+ */
+ private void rewriteSingle(Rewriter rewriter, String name) throws IOException {
+ if (LOGD) Slog.d(TAG, "rewriting " + name);
+
+ final File file = new File(mBasePath, name);
final File backupFile;
+ rewriter.reset();
+
if (file.exists()) {
// read existing data
- readFile(file, reader);
+ readFile(file, rewriter);
+
+ // skip when rewriter has nothing to write
+ if (!rewriter.shouldWrite()) return;
// backup existing data during write
- backupFile = new File(mBasePath, activeName + SUFFIX_BACKUP);
+ backupFile = new File(mBasePath, name + SUFFIX_BACKUP);
file.renameTo(backupFile);
try {
- writeFile(file, writer);
+ writeFile(file, rewriter);
// write success, delete backup
backupFile.delete();
@@ -148,11 +233,11 @@
} else {
// create empty backup during write
- backupFile = new File(mBasePath, activeName + SUFFIX_NO_BACKUP);
+ backupFile = new File(mBasePath, name + SUFFIX_NO_BACKUP);
backupFile.createNewFile();
try {
- writeFile(file, writer);
+ writeFile(file, rewriter);
// write success, delete empty backup
backupFile.delete();
@@ -176,6 +261,8 @@
// read file when it overlaps
if (info.startMillis <= matchEndMillis && matchStartMillis <= info.endMillis) {
+ if (LOGD) Slog.d(TAG, "reading matching " + name);
+
final File file = new File(mBasePath, name);
readFile(file, reader);
}
@@ -224,16 +311,20 @@
if (!info.parse(name)) continue;
if (info.isActive()) {
- // found active file; rotate if old enough
- if (info.startMillis < rotateBefore) {
+ if (info.startMillis <= rotateBefore) {
+ // found active file; rotate if old enough
+ if (LOGD) Slog.d(TAG, "rotating " + name);
+
info.endMillis = currentTimeMillis;
final File file = new File(mBasePath, name);
final File destFile = new File(mBasePath, info.build());
file.renameTo(destFile);
}
- } else if (info.endMillis < deleteBefore) {
+ } else if (info.endMillis <= deleteBefore) {
// found rotated file; delete if old enough
+ if (LOGD) Slog.d(TAG, "deleting " + name);
+
final File file = new File(mBasePath, name);
file.delete();
}
diff --git a/core/java/com/android/internal/util/IndentingPrintWriter.java b/core/java/com/android/internal/util/IndentingPrintWriter.java
new file mode 100644
index 0000000..3dd2284
--- /dev/null
+++ b/core/java/com/android/internal/util/IndentingPrintWriter.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+package com.android.internal.util;
+
+import java.io.PrintWriter;
+import java.io.Writer;
+
+/**
+ * Lightweight wrapper around {@link PrintWriter} that automatically indents
+ * newlines based on internal state. Delays writing indent until first actual
+ * write on a newline, enabling indent modification after newline.
+ */
+public class IndentingPrintWriter extends PrintWriter {
+ private final String mIndent;
+
+ private StringBuilder mBuilder = new StringBuilder();
+ private String mCurrent = new String();
+ private boolean mEmptyLine = true;
+
+ public IndentingPrintWriter(Writer writer, String indent) {
+ super(writer);
+ mIndent = indent;
+ }
+
+ public void increaseIndent() {
+ mBuilder.append(mIndent);
+ mCurrent = mBuilder.toString();
+ }
+
+ public void decreaseIndent() {
+ mBuilder.delete(0, mIndent.length());
+ mCurrent = mBuilder.toString();
+ }
+
+ @Override
+ public void println() {
+ super.println();
+ mEmptyLine = true;
+ }
+
+ @Override
+ public void write(char[] buf, int offset, int count) {
+ if (mEmptyLine) {
+ mEmptyLine = false;
+ super.print(mCurrent);
+ }
+ super.write(buf, offset, count);
+ }
+}
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index a235d9a..9024d8d 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -387,9 +387,9 @@
}
}
- public boolean deleteSurroundingText(int leftLength, int rightLength) {
+ public boolean deleteSurroundingText(int beforeLength, int afterLength) {
try {
- mIInputContext.deleteSurroundingText(leftLength, rightLength);
+ mIInputContext.deleteSurroundingText(beforeLength, afterLength);
return true;
} catch (RemoteException e) {
return false;
diff --git a/docs/html/guide/developing/device.jd b/docs/html/guide/developing/device.jd
index c4d08ed..e46d07c 100644
--- a/docs/html/guide/developing/device.jd
+++ b/docs/html/guide/developing/device.jd
@@ -154,6 +154,14 @@
<td><code>0489</code></td>
</tr>
<tr>
+ <td>Fujitsu</td>
+ <td><code>04C5</code></td>
+ </tr>
+ <tr>
+ <td>Fujitsu Toshiba</td>
+ <td><code>04C5</code></td>
+ </tr>
+ <tr>
<td>Garmin-Asus</td>
<td><code>091E</code></td>
</tr>
diff --git a/media/libstagefright/codecs/aacenc/src/adj_thr.c b/media/libstagefright/codecs/aacenc/src/adj_thr.c
index c656f65..a8ab809 100644
--- a/media/libstagefright/codecs/aacenc/src/adj_thr.c
+++ b/media/libstagefright/codecs/aacenc/src/adj_thr.c
@@ -1039,7 +1039,7 @@
/* minSnr adaptation */
/* maximum reduction of minSnr goes down to minSnr^maxRed */
- msaParam->maxRed = 0x20000000; /* *0.25f /
+ msaParam->maxRed = 0x20000000; /* *0.25f */
/* start adaptation of minSnr for avgEn/sfbEn > startRatio */
msaParam->startRatio = 0x0ccccccd; /* 10 */
/* maximum minSnr reduction to minSnr^maxRed is reached for
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 69a247d..d33ed3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -23,6 +23,8 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Message;
import android.os.ServiceManager;
import android.util.AttributeSet;
import android.util.Slog;
@@ -66,6 +68,35 @@
int mDisabledFlags = 0;
int mNavigationIconHints = 0;
+ // workaround for LayoutTransitions leaving the nav buttons in a weird state (bug 5549288)
+ final static boolean WORKAROUND_INVALID_LAYOUT = true;
+ final static int MSG_CHECK_INVALID_LAYOUT = 8686;
+
+ private class H extends Handler {
+ public void handleMessage(Message m) {
+ switch (m.what) {
+ case MSG_CHECK_INVALID_LAYOUT:
+ final String how = "" + m.obj;
+ final int w = getWidth();
+ final int h = getHeight();
+ final int vw = mCurrentView.getWidth();
+ final int vh = mCurrentView.getHeight();
+
+ if (h != vh || w != vw) {
+ Slog.w(TAG, String.format(
+ "*** Invalid layout in navigation bar (%s this=%dx%d cur=%dx%d)",
+ how, w, h, vw, vh));
+ if (WORKAROUND_INVALID_LAYOUT) {
+ requestLayout();
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ private H mHandler = new H();
+
public View getRecentsButton() {
return mCurrentView.findViewById(R.id.recent_apps);
}
@@ -275,6 +306,36 @@
}
}
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ if (DEBUG) Slog.d(TAG, String.format(
+ "onSizeChanged: (%dx%d) old: (%dx%d)", w, h, oldw, oldh));
+ postCheckForInvalidLayout("sizeChanged");
+ super.onSizeChanged(w, h, oldw, oldh);
+ }
+
+ /*
+ @Override
+ protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
+ if (DEBUG) Slog.d(TAG, String.format(
+ "onLayout: %s (%d,%d,%d,%d)",
+ changed?"changed":"notchanged", left, top, right, bottom));
+ super.onLayout(changed, left, top, right, bottom);
+ }
+
+ // uncomment this for extra defensiveness in WORKAROUND_INVALID_LAYOUT situations: if all else
+ // fails, any touch on the display will fix the layout.
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (DEBUG) Slog.d(TAG, "onInterceptTouchEvent: " + ev.toString());
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ postCheckForInvalidLayout("touch");
+ }
+ return super.onInterceptTouchEvent(ev);
+ }
+ */
+
+
private String getResourceName(int resId) {
if (resId != 0) {
final android.content.res.Resources res = mContext.getResources();
@@ -288,6 +349,10 @@
}
}
+ private void postCheckForInvalidLayout(final String how) {
+ mHandler.obtainMessage(MSG_CHECK_INVALID_LAYOUT, 0, 0, how).sendToTarget();
+ }
+
private static String visibilityToString(int vis) {
switch (vis) {
case View.INVISIBLE:
diff --git a/packages/VpnDialogs/AndroidManifest.xml b/packages/VpnDialogs/AndroidManifest.xml
index 8e062b7..46c0f83 100644
--- a/packages/VpnDialogs/AndroidManifest.xml
+++ b/packages/VpnDialogs/AndroidManifest.xml
@@ -5,7 +5,7 @@
<application android:label="VpnDialogs"
android:allowBackup="false" >
<activity android:name=".ConfirmDialog"
- android:theme="@style/transparent">
+ android:theme="@*android:style/Theme.Holo.Dialog.Alert">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.DEFAULT"/>
@@ -13,7 +13,7 @@
</activity>
<activity android:name=".ManageDialog"
- android:theme="@style/transparent"
+ android:theme="@*android:style/Theme.Holo.Dialog.Alert"
android:noHistory="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
diff --git a/packages/VpnDialogs/res/values/styles.xml b/packages/VpnDialogs/res/values/styles.xml
index cf10596..e3469ec 100644
--- a/packages/VpnDialogs/res/values/styles.xml
+++ b/packages/VpnDialogs/res/values/styles.xml
@@ -15,13 +15,6 @@
-->
<resources>
-
- <style name="transparent">
- <item name="android:windowBackground">@android:color/transparent</item>
- <item name="android:windowNoTitle">true</item>
- <item name="android:windowIsFloating">true</item>
- </style>
-
<style name="label">
<item name="android:gravity">center_vertical|right</item>
<item name="android:paddingRight">2mm</item>
diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java
index c7b4a5f..13d8019 100644
--- a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java
+++ b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java
@@ -16,8 +16,6 @@
package com.android.vpndialogs;
-import android.app.Activity;
-import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -32,15 +30,16 @@
import android.widget.ImageView;
import android.widget.TextView;
-public class ConfirmDialog extends Activity implements CompoundButton.OnCheckedChangeListener,
- DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
+import com.android.internal.app.AlertActivity;
+
+public class ConfirmDialog extends AlertActivity implements
+ CompoundButton.OnCheckedChangeListener, DialogInterface.OnClickListener {
private static final String TAG = "VpnConfirm";
private String mPackage;
private IConnectivityManager mService;
- private AlertDialog mDialog;
private Button mButton;
@Override
@@ -67,18 +66,17 @@
getString(R.string.prompt, app.loadLabel(pm)));
((CompoundButton) view.findViewById(R.id.check)).setOnCheckedChangeListener(this);
- mDialog = new AlertDialog.Builder(this)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setTitle(android.R.string.dialog_alert_title)
- .setView(view)
- .setPositiveButton(android.R.string.ok, this)
- .setNegativeButton(android.R.string.cancel, this)
- .setCancelable(false)
- .create();
- mDialog.setOnDismissListener(this);
- mDialog.show();
+ mAlertParams.mIconId = android.R.drawable.ic_dialog_alert;
+ mAlertParams.mTitle = getText(android.R.string.dialog_alert_title);
+ mAlertParams.mPositiveButtonText = getText(android.R.string.ok);
+ mAlertParams.mPositiveButtonListener = this;
+ mAlertParams.mNegativeButtonText = getText(android.R.string.cancel);
+ mAlertParams.mNegativeButtonListener = this;
+ mAlertParams.mView = view;
+ setupAlert();
- mButton = mDialog.getButton(DialogInterface.BUTTON_POSITIVE);
+ getWindow().setCloseOnTouchOutside(false);
+ mButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
mButton.setEnabled(false);
} catch (Exception e) {
Log.e(TAG, "onResume", e);
@@ -87,12 +85,7 @@
}
@Override
- protected void onPause() {
- super.onPause();
- if (mDialog != null) {
- mDialog.setOnDismissListener(null);
- mDialog.dismiss();
- }
+ public void onBackPressed() {
}
@Override
@@ -103,16 +96,11 @@
@Override
public void onClick(DialogInterface dialog, int which) {
try {
- if (which == AlertDialog.BUTTON_POSITIVE && mService.prepareVpn(null, mPackage)) {
+ if (which == DialogInterface.BUTTON_POSITIVE && mService.prepareVpn(null, mPackage)) {
setResult(RESULT_OK);
}
} catch (Exception e) {
Log.e(TAG, "onClick", e);
}
}
-
- @Override
- public void onDismiss(DialogInterface dialog) {
- finish();
- }
}
diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java
index 7fb1417..2de0251 100644
--- a/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java
+++ b/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java
@@ -16,8 +16,6 @@
package com.android.vpndialogs;
-import android.app.Activity;
-import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -35,20 +33,20 @@
import android.widget.ImageView;
import android.widget.TextView;
+import com.android.internal.app.AlertActivity;
import com.android.internal.net.VpnConfig;
import java.io.DataInputStream;
import java.io.FileInputStream;
-public class ManageDialog extends Activity implements Handler.Callback,
- DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
+public class ManageDialog extends AlertActivity implements
+ DialogInterface.OnClickListener, Handler.Callback {
private static final String TAG = "VpnManage";
private VpnConfig mConfig;
private IConnectivityManager mService;
- private AlertDialog mDialog;
private TextView mDuration;
private TextView mDataTransmitted;
private TextView mDataReceived;
@@ -80,31 +78,24 @@
mDataReceived = (TextView) view.findViewById(R.id.data_received);
if (mConfig.user.equals(VpnConfig.LEGACY_VPN)) {
- mDialog = new AlertDialog.Builder(this)
- .setIcon(android.R.drawable.ic_dialog_info)
- .setTitle(R.string.legacy_title)
- .setView(view)
- .setNeutralButton(R.string.disconnect, this)
- .setNegativeButton(android.R.string.cancel, this)
- .create();
+ mAlertParams.mIconId = android.R.drawable.ic_dialog_info;
+ mAlertParams.mTitle = getText(R.string.legacy_title);
} else {
PackageManager pm = getPackageManager();
ApplicationInfo app = pm.getApplicationInfo(mConfig.user, 0);
- mDialog = new AlertDialog.Builder(this)
- .setIcon(app.loadIcon(pm))
- .setTitle(app.loadLabel(pm))
- .setView(view)
- .setNeutralButton(R.string.disconnect, this)
- .setNegativeButton(android.R.string.cancel, this)
- .create();
+ mAlertParams.mIcon = app.loadIcon(pm);
+ mAlertParams.mTitle = app.loadLabel(pm);
}
-
if (mConfig.configureIntent != null) {
- mDialog.setButton(DialogInterface.BUTTON_POSITIVE,
- getText(R.string.configure), this);
+ mAlertParams.mPositiveButtonText = getText(R.string.configure);
+ mAlertParams.mPositiveButtonListener = this;
}
- mDialog.setOnDismissListener(this);
- mDialog.show();
+ mAlertParams.mNeutralButtonText = getText(R.string.disconnect);
+ mAlertParams.mNeutralButtonListener = this;
+ mAlertParams.mNegativeButtonText = getText(android.R.string.cancel);
+ mAlertParams.mNegativeButtonListener = this;
+ mAlertParams.mView = view;
+ setupAlert();
if (mHandler == null) {
mHandler = new Handler(this);
@@ -119,18 +110,17 @@
@Override
protected void onPause() {
super.onPause();
- if (mDialog != null) {
- mDialog.setOnDismissListener(null);
- mDialog.dismiss();
+ if (!isFinishing()) {
+ finish();
}
}
@Override
public void onClick(DialogInterface dialog, int which) {
try {
- if (which == AlertDialog.BUTTON_POSITIVE) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
mConfig.configureIntent.send();
- } else if (which == AlertDialog.BUTTON_NEUTRAL) {
+ } else if (which == DialogInterface.BUTTON_NEUTRAL) {
mService.prepareVpn(mConfig.user, VpnConfig.LEGACY_VPN);
}
} catch (Exception e) {
@@ -140,15 +130,10 @@
}
@Override
- public void onDismiss(DialogInterface dialog) {
- finish();
- }
-
- @Override
public boolean handleMessage(Message message) {
mHandler.removeMessages(0);
- if (mDialog.isShowing()) {
+ if (!isFinishing()) {
if (mConfig.startTime != 0) {
long seconds = (SystemClock.elapsedRealtime() - mConfig.startTime) / 1000;
mDuration.setText(String.format("%02d:%02d:%02d",
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 43a34d0..2d6e4f8 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -2044,6 +2044,12 @@
mTmpNavigationFrame.offset(mNavigationBarWidth, 0);
}
}
+ // Make sure the content and current rectangles are updated to
+ // account for the restrictions from the navigation bar.
+ mContentTop = mCurTop = mDockTop;
+ mContentBottom = mCurBottom = mDockBottom;
+ mContentLeft = mCurLeft = mDockLeft;
+ mContentRight = mCurRight = mDockRight;
// And compute the final frame.
mNavigationBar.computeFrameLw(mTmpNavigationFrame, mTmpNavigationFrame,
mTmpNavigationFrame, mTmpNavigationFrame);
diff --git a/services/java/com/android/server/EventLogTags.logtags b/services/java/com/android/server/EventLogTags.logtags
index 4dad209..0bcec2e 100644
--- a/services/java/com/android/server/EventLogTags.logtags
+++ b/services/java/com/android/server/EventLogTags.logtags
@@ -142,5 +142,5 @@
# ---------------------------
# NetworkStatsService.java
# ---------------------------
-51100 netstats_mobile_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3),(dev_history_start|2|3)
-51101 netstats_wifi_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3),(dev_history_start|2|3)
+51100 netstats_mobile_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3)
+51101 netstats_wifi_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3)
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index 51adebe..a71ccb5 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -1573,6 +1573,9 @@
private long getTotalBytes(NetworkTemplate template, long start, long end) {
try {
return mNetworkStats.getSummaryForNetwork(template, start, end).getTotalBytes();
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "problem reading network stats: " + e);
+ return 0;
} catch (RemoteException e) {
// ignored; service lives in system_server
return 0;
diff --git a/services/java/com/android/server/net/NetworkStatsCollection.java b/services/java/com/android/server/net/NetworkStatsCollection.java
new file mode 100644
index 0000000..70038d9
--- /dev/null
+++ b/services/java/com/android/server/net/NetworkStatsCollection.java
@@ -0,0 +1,510 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+package com.android.server.net;
+
+import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.SET_ALL;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+import static android.net.TrafficStats.UID_REMOVED;
+
+import android.net.NetworkIdentity;
+import android.net.NetworkStats;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
+import android.net.TrafficStats;
+import android.text.format.DateUtils;
+
+import com.android.internal.os.AtomicFile;
+import com.android.internal.util.FileRotator;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Objects;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.ProtocolException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import libcore.io.IoUtils;
+
+/**
+ * Collection of {@link NetworkStatsHistory}, stored based on combined key of
+ * {@link NetworkIdentitySet}, UID, set, and tag. Knows how to persist itself.
+ */
+public class NetworkStatsCollection implements FileRotator.Reader {
+ private static final String TAG = "NetworkStatsCollection";
+
+ /** File header magic number: "ANET" */
+ private static final int FILE_MAGIC = 0x414E4554;
+
+ private static final int VERSION_NETWORK_INIT = 1;
+
+ private static final int VERSION_UID_INIT = 1;
+ private static final int VERSION_UID_WITH_IDENT = 2;
+ private static final int VERSION_UID_WITH_TAG = 3;
+ private static final int VERSION_UID_WITH_SET = 4;
+
+ private static final int VERSION_UNIFIED_INIT = 16;
+
+ private HashMap<Key, NetworkStatsHistory> mStats = Maps.newHashMap();
+
+ private long mBucketDuration;
+
+ private long mStartMillis;
+ private long mEndMillis;
+ private long mTotalBytes;
+ private boolean mDirty;
+
+ public NetworkStatsCollection(long bucketDuration) {
+ mBucketDuration = bucketDuration;
+ reset();
+ }
+
+ public void reset() {
+ mStats.clear();
+ mStartMillis = Long.MAX_VALUE;
+ mEndMillis = Long.MIN_VALUE;
+ mTotalBytes = 0;
+ mDirty = false;
+ }
+
+ public long getStartMillis() {
+ return mStartMillis;
+ }
+
+ public long getEndMillis() {
+ return mEndMillis;
+ }
+
+ public long getTotalBytes() {
+ return mTotalBytes;
+ }
+
+ public boolean isDirty() {
+ return mDirty;
+ }
+
+ public void clearDirty() {
+ mDirty = false;
+ }
+
+ public boolean isEmpty() {
+ return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE;
+ }
+
+ /**
+ * Combine all {@link NetworkStatsHistory} in this collection which match
+ * the requested parameters.
+ */
+ public NetworkStatsHistory getHistory(
+ NetworkTemplate template, int uid, int set, int tag, int fields) {
+ final NetworkStatsHistory combined = new NetworkStatsHistory(
+ mBucketDuration, estimateBuckets(), fields);
+ for (Map.Entry<Key, NetworkStatsHistory> entry : mStats.entrySet()) {
+ final Key key = entry.getKey();
+ final boolean setMatches = set == SET_ALL || key.set == set;
+ if (key.uid == uid && setMatches && key.tag == tag
+ && templateMatches(template, key.ident)) {
+ combined.recordEntireHistory(entry.getValue());
+ }
+ }
+ return combined;
+ }
+
+ /**
+ * Summarize all {@link NetworkStatsHistory} in this collection which match
+ * the requested parameters.
+ */
+ public NetworkStats getSummary(NetworkTemplate template, long start, long end) {
+ final long now = System.currentTimeMillis();
+
+ final NetworkStats stats = new NetworkStats(end - start, 24);
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+ NetworkStatsHistory.Entry historyEntry = null;
+
+ for (Map.Entry<Key, NetworkStatsHistory> mapEntry : mStats.entrySet()) {
+ final Key key = mapEntry.getKey();
+ if (templateMatches(template, key.ident)) {
+ final NetworkStatsHistory history = mapEntry.getValue();
+ historyEntry = history.getValues(start, end, now, historyEntry);
+
+ entry.iface = IFACE_ALL;
+ entry.uid = key.uid;
+ entry.set = key.set;
+ entry.tag = key.tag;
+ entry.rxBytes = historyEntry.rxBytes;
+ entry.rxPackets = historyEntry.rxPackets;
+ entry.txBytes = historyEntry.txBytes;
+ entry.txPackets = historyEntry.txPackets;
+ entry.operations = historyEntry.operations;
+
+ if (!entry.isEmpty()) {
+ stats.combineValues(entry);
+ }
+ }
+ }
+
+ return stats;
+ }
+
+ /**
+ * Record given {@link NetworkStats.Entry} into this collection.
+ */
+ public void recordData(NetworkIdentitySet ident, int uid, int set, int tag, long start,
+ long end, NetworkStats.Entry entry) {
+ noteRecordedHistory(start, end, entry.rxBytes + entry.txBytes);
+ findOrCreateHistory(ident, uid, set, tag).recordData(start, end, entry);
+ }
+
+ /**
+ * Record given {@link NetworkStatsHistory} into this collection.
+ */
+ private void recordHistory(Key key, NetworkStatsHistory history) {
+ if (history.size() == 0) return;
+ noteRecordedHistory(history.getStart(), history.getEnd(), history.getTotalBytes());
+
+ final NetworkStatsHistory existing = mStats.get(key);
+ if (existing != null) {
+ existing.recordEntireHistory(history);
+ } else {
+ mStats.put(key, history);
+ }
+ }
+
+ /**
+ * Record all {@link NetworkStatsHistory} contained in the given collection
+ * into this collection.
+ */
+ public void recordCollection(NetworkStatsCollection another) {
+ for (Map.Entry<Key, NetworkStatsHistory> entry : another.mStats.entrySet()) {
+ recordHistory(entry.getKey(), entry.getValue());
+ }
+ }
+
+ private NetworkStatsHistory findOrCreateHistory(
+ NetworkIdentitySet ident, int uid, int set, int tag) {
+ final Key key = new Key(ident, uid, set, tag);
+ final NetworkStatsHistory existing = mStats.get(key);
+
+ // update when no existing, or when bucket duration changed
+ NetworkStatsHistory updated = null;
+ if (existing == null) {
+ updated = new NetworkStatsHistory(mBucketDuration, 10);
+ } else if (existing.getBucketDuration() != mBucketDuration) {
+ updated = new NetworkStatsHistory(existing, mBucketDuration);
+ }
+
+ if (updated != null) {
+ mStats.put(key, updated);
+ return updated;
+ } else {
+ return existing;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void read(InputStream in) throws IOException {
+ read(new DataInputStream(in));
+ }
+
+ public void read(DataInputStream in) throws IOException {
+ // verify file magic header intact
+ final int magic = in.readInt();
+ if (magic != FILE_MAGIC) {
+ throw new ProtocolException("unexpected magic: " + magic);
+ }
+
+ final int version = in.readInt();
+ switch (version) {
+ case VERSION_UNIFIED_INIT: {
+ // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
+ final int identSize = in.readInt();
+ for (int i = 0; i < identSize; i++) {
+ final NetworkIdentitySet ident = new NetworkIdentitySet(in);
+
+ final int size = in.readInt();
+ for (int j = 0; j < size; j++) {
+ final int uid = in.readInt();
+ final int set = in.readInt();
+ final int tag = in.readInt();
+
+ final Key key = new Key(ident, uid, set, tag);
+ final NetworkStatsHistory history = new NetworkStatsHistory(in);
+ recordHistory(key, history);
+ }
+ }
+ break;
+ }
+ default: {
+ throw new ProtocolException("unexpected version: " + version);
+ }
+ }
+ }
+
+ public void write(DataOutputStream out) throws IOException {
+ // cluster key lists grouped by ident
+ final HashMap<NetworkIdentitySet, ArrayList<Key>> keysByIdent = Maps.newHashMap();
+ for (Key key : mStats.keySet()) {
+ ArrayList<Key> keys = keysByIdent.get(key.ident);
+ if (keys == null) {
+ keys = Lists.newArrayList();
+ keysByIdent.put(key.ident, keys);
+ }
+ keys.add(key);
+ }
+
+ out.writeInt(FILE_MAGIC);
+ out.writeInt(VERSION_UNIFIED_INIT);
+
+ out.writeInt(keysByIdent.size());
+ for (NetworkIdentitySet ident : keysByIdent.keySet()) {
+ final ArrayList<Key> keys = keysByIdent.get(ident);
+ ident.writeToStream(out);
+
+ out.writeInt(keys.size());
+ for (Key key : keys) {
+ final NetworkStatsHistory history = mStats.get(key);
+ out.writeInt(key.uid);
+ out.writeInt(key.set);
+ out.writeInt(key.tag);
+ history.writeToStream(out);
+ }
+ }
+
+ out.flush();
+ }
+
+ @Deprecated
+ public void readLegacyNetwork(File file) throws IOException {
+ final AtomicFile inputFile = new AtomicFile(file);
+
+ DataInputStream in = null;
+ try {
+ in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
+
+ // verify file magic header intact
+ final int magic = in.readInt();
+ if (magic != FILE_MAGIC) {
+ throw new ProtocolException("unexpected magic: " + magic);
+ }
+
+ final int version = in.readInt();
+ switch (version) {
+ case VERSION_NETWORK_INIT: {
+ // network := size *(NetworkIdentitySet NetworkStatsHistory)
+ final int size = in.readInt();
+ for (int i = 0; i < size; i++) {
+ final NetworkIdentitySet ident = new NetworkIdentitySet(in);
+ final NetworkStatsHistory history = new NetworkStatsHistory(in);
+
+ final Key key = new Key(ident, UID_ALL, SET_ALL, TAG_NONE);
+ recordHistory(key, history);
+ }
+ break;
+ }
+ default: {
+ throw new ProtocolException("unexpected version: " + version);
+ }
+ }
+ } catch (FileNotFoundException e) {
+ // missing stats is okay, probably first boot
+ } finally {
+ IoUtils.closeQuietly(in);
+ }
+ }
+
+ @Deprecated
+ public void readLegacyUid(File file, boolean onlyTags) throws IOException {
+ final AtomicFile inputFile = new AtomicFile(file);
+
+ DataInputStream in = null;
+ try {
+ in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
+
+ // verify file magic header intact
+ final int magic = in.readInt();
+ if (magic != FILE_MAGIC) {
+ throw new ProtocolException("unexpected magic: " + magic);
+ }
+
+ final int version = in.readInt();
+ switch (version) {
+ case VERSION_UID_INIT: {
+ // uid := size *(UID NetworkStatsHistory)
+
+ // drop this data version, since we don't have a good
+ // mapping into NetworkIdentitySet.
+ break;
+ }
+ case VERSION_UID_WITH_IDENT: {
+ // uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory))
+
+ // drop this data version, since this version only existed
+ // for a short time.
+ break;
+ }
+ case VERSION_UID_WITH_TAG:
+ case VERSION_UID_WITH_SET: {
+ // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
+ final int identSize = in.readInt();
+ for (int i = 0; i < identSize; i++) {
+ final NetworkIdentitySet ident = new NetworkIdentitySet(in);
+
+ final int size = in.readInt();
+ for (int j = 0; j < size; j++) {
+ final int uid = in.readInt();
+ final int set = (version >= VERSION_UID_WITH_SET) ? in.readInt()
+ : SET_DEFAULT;
+ final int tag = in.readInt();
+
+ final Key key = new Key(ident, uid, set, tag);
+ final NetworkStatsHistory history = new NetworkStatsHistory(in);
+
+ if ((tag == TAG_NONE) != onlyTags) {
+ recordHistory(key, history);
+ }
+ }
+ }
+ break;
+ }
+ default: {
+ throw new ProtocolException("unexpected version: " + version);
+ }
+ }
+ } catch (FileNotFoundException e) {
+ // missing stats is okay, probably first boot
+ } finally {
+ IoUtils.closeQuietly(in);
+ }
+ }
+
+ /**
+ * Remove any {@link NetworkStatsHistory} attributed to the requested UID,
+ * moving any {@link NetworkStats#TAG_NONE} series to
+ * {@link TrafficStats#UID_REMOVED}.
+ */
+ public void removeUid(int uid) {
+ final ArrayList<Key> knownKeys = Lists.newArrayList();
+ knownKeys.addAll(mStats.keySet());
+
+ // migrate all UID stats into special "removed" bucket
+ for (Key key : knownKeys) {
+ if (key.uid == uid) {
+ // only migrate combined TAG_NONE history
+ if (key.tag == TAG_NONE) {
+ final NetworkStatsHistory uidHistory = mStats.get(key);
+ final NetworkStatsHistory removedHistory = findOrCreateHistory(
+ key.ident, UID_REMOVED, SET_DEFAULT, TAG_NONE);
+ removedHistory.recordEntireHistory(uidHistory);
+ }
+ mStats.remove(key);
+ mDirty = true;
+ }
+ }
+ }
+
+ private void noteRecordedHistory(long startMillis, long endMillis, long totalBytes) {
+ if (startMillis < mStartMillis) mStartMillis = startMillis;
+ if (endMillis > mEndMillis) mEndMillis = endMillis;
+ mTotalBytes += totalBytes;
+ mDirty = true;
+ }
+
+ private int estimateBuckets() {
+ return (int) (Math.min(mEndMillis - mStartMillis, DateUtils.WEEK_IN_MILLIS * 5)
+ / mBucketDuration);
+ }
+
+ public void dump(IndentingPrintWriter pw) {
+ final ArrayList<Key> keys = Lists.newArrayList();
+ keys.addAll(mStats.keySet());
+ Collections.sort(keys);
+
+ for (Key key : keys) {
+ pw.print("ident="); pw.print(key.ident.toString());
+ pw.print(" uid="); pw.print(key.uid);
+ pw.print(" set="); pw.print(NetworkStats.setToString(key.set));
+ pw.print(" tag="); pw.println(NetworkStats.tagToString(key.tag));
+
+ final NetworkStatsHistory history = mStats.get(key);
+ pw.increaseIndent();
+ history.dump(pw, true);
+ pw.decreaseIndent();
+ }
+ }
+
+ /**
+ * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity}
+ * in the given {@link NetworkIdentitySet}.
+ */
+ private static boolean templateMatches(NetworkTemplate template, NetworkIdentitySet identSet) {
+ for (NetworkIdentity ident : identSet) {
+ if (template.matches(ident)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static class Key implements Comparable<Key> {
+ public final NetworkIdentitySet ident;
+ public final int uid;
+ public final int set;
+ public final int tag;
+
+ private final int hashCode;
+
+ public Key(NetworkIdentitySet ident, int uid, int set, int tag) {
+ this.ident = ident;
+ this.uid = uid;
+ this.set = set;
+ this.tag = tag;
+ hashCode = Objects.hashCode(ident, uid, set, tag);
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof Key) {
+ final Key key = (Key) obj;
+ return uid == key.uid && set == key.set && tag == key.tag
+ && Objects.equal(ident, key.ident);
+ }
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public int compareTo(Key another) {
+ return Integer.compare(uid, another.uid);
+ }
+ }
+}
diff --git a/services/java/com/android/server/net/NetworkStatsRecorder.java b/services/java/com/android/server/net/NetworkStatsRecorder.java
new file mode 100644
index 0000000..e7ba358
--- /dev/null
+++ b/services/java/com/android/server/net/NetworkStatsRecorder.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+package com.android.server.net;
+
+import static android.net.NetworkStats.TAG_NONE;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.net.NetworkStats;
+import android.net.NetworkStats.NonMonotonicObserver;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
+import android.net.TrafficStats;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.util.FileRotator;
+import com.android.internal.util.IndentingPrintWriter;
+import com.google.android.collect.Sets;
+
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.ref.WeakReference;
+import java.util.HashSet;
+import java.util.Map;
+
+/**
+ * Logic to record deltas between periodic {@link NetworkStats} snapshots into
+ * {@link NetworkStatsHistory} that belong to {@link NetworkStatsCollection}.
+ * Keeps pending changes in memory until they pass a specific threshold, in
+ * bytes. Uses {@link FileRotator} for persistence logic.
+ * <p>
+ * Not inherently thread safe.
+ */
+public class NetworkStatsRecorder {
+ private static final String TAG = "NetworkStatsRecorder";
+ private static final boolean LOGD = true;
+
+ private final FileRotator mRotator;
+ private final NonMonotonicObserver<String> mObserver;
+ private final String mCookie;
+
+ private final long mBucketDuration;
+ private final long mPersistThresholdBytes;
+ private final boolean mOnlyTags;
+
+ private NetworkStats mLastSnapshot;
+
+ private final NetworkStatsCollection mPending;
+ private final NetworkStatsCollection mSinceBoot;
+
+ private final CombiningRewriter mPendingRewriter;
+
+ private WeakReference<NetworkStatsCollection> mComplete;
+
+ public NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer,
+ String cookie, long bucketDuration, long persistThresholdBytes, boolean onlyTags) {
+ mRotator = checkNotNull(rotator, "missing FileRotator");
+ mObserver = checkNotNull(observer, "missing NonMonotonicObserver");
+ mCookie = cookie;
+
+ mBucketDuration = bucketDuration;
+ mPersistThresholdBytes = persistThresholdBytes;
+ mOnlyTags = onlyTags;
+
+ mPending = new NetworkStatsCollection(bucketDuration);
+ mSinceBoot = new NetworkStatsCollection(bucketDuration);
+
+ mPendingRewriter = new CombiningRewriter(mPending);
+ }
+
+ public void resetLocked() {
+ mLastSnapshot = null;
+ mPending.reset();
+ mSinceBoot.reset();
+ mComplete.clear();
+ }
+
+ public NetworkStats.Entry getTotalSinceBootLocked(NetworkTemplate template) {
+ return mSinceBoot.getSummary(template, Long.MIN_VALUE, Long.MAX_VALUE).getTotal(null);
+ }
+
+ /**
+ * Load complete history represented by {@link FileRotator}. Caches
+ * internally as a {@link WeakReference}, and updated with future
+ * {@link #recordSnapshotLocked(NetworkStats, Map, long)} snapshots as long
+ * as reference is valid.
+ */
+ public NetworkStatsCollection getOrLoadCompleteLocked() {
+ NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
+ if (complete == null) {
+ if (LOGD) Slog.d(TAG, "getOrLoadCompleteLocked() reading from disk for " + mCookie);
+ try {
+ complete = new NetworkStatsCollection(mBucketDuration);
+ mRotator.readMatching(complete, Long.MIN_VALUE, Long.MAX_VALUE);
+ complete.recordCollection(mPending);
+ mComplete = new WeakReference<NetworkStatsCollection>(complete);
+ } catch (IOException e) {
+ Log.wtf(TAG, "problem completely reading network stats", e);
+ }
+ }
+ return complete;
+ }
+
+ /**
+ * Record any delta that occurred since last {@link NetworkStats} snapshot,
+ * using the given {@link Map} to identify network interfaces. First
+ * snapshot is considered bootstrap, and is not counted as delta.
+ */
+ public void recordSnapshotLocked(NetworkStats snapshot,
+ Map<String, NetworkIdentitySet> ifaceIdent, long currentTimeMillis) {
+ final HashSet<String> unknownIfaces = Sets.newHashSet();
+
+ // assume first snapshot is bootstrap and don't record
+ if (mLastSnapshot == null) {
+ mLastSnapshot = snapshot;
+ return;
+ }
+
+ final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
+
+ final NetworkStats delta = NetworkStats.subtract(
+ snapshot, mLastSnapshot, mObserver, mCookie);
+ final long end = currentTimeMillis;
+ final long start = end - delta.getElapsedRealtime();
+
+ NetworkStats.Entry entry = null;
+ for (int i = 0; i < delta.size(); i++) {
+ entry = delta.getValues(i, entry);
+ final NetworkIdentitySet ident = ifaceIdent.get(entry.iface);
+ if (ident == null) {
+ unknownIfaces.add(entry.iface);
+ continue;
+ }
+
+ // skip when no delta occured
+ if (entry.isEmpty()) continue;
+
+ // only record tag data when requested
+ if ((entry.tag == TAG_NONE) != mOnlyTags) {
+ mPending.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
+
+ // also record against boot stats when present
+ if (mSinceBoot != null) {
+ mSinceBoot.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
+ }
+
+ // also record against complete dataset when present
+ if (complete != null) {
+ complete.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
+ }
+ }
+ }
+
+ mLastSnapshot = snapshot;
+
+ if (LOGD && unknownIfaces.size() > 0) {
+ Slog.w(TAG, "unknown interfaces " + unknownIfaces + ", ignoring those stats");
+ }
+ }
+
+ /**
+ * Consider persisting any pending deltas, if they are beyond
+ * {@link #mPersistThresholdBytes}.
+ */
+ public void maybePersistLocked(long currentTimeMillis) {
+ final long pendingBytes = mPending.getTotalBytes();
+ if (pendingBytes >= mPersistThresholdBytes) {
+ forcePersistLocked(currentTimeMillis);
+ } else {
+ mRotator.maybeRotate(currentTimeMillis);
+ }
+ }
+
+ /**
+ * Force persisting any pending deltas.
+ */
+ public void forcePersistLocked(long currentTimeMillis) {
+ if (mPending.isDirty()) {
+ if (LOGD) Slog.d(TAG, "forcePersistLocked() writing for " + mCookie);
+ try {
+ mRotator.rewriteActive(mPendingRewriter, currentTimeMillis);
+ mRotator.maybeRotate(currentTimeMillis);
+ mPending.reset();
+ } catch (IOException e) {
+ Log.wtf(TAG, "problem persisting pending stats", e);
+ }
+ }
+ }
+
+ /**
+ * Remove the given UID from all {@link FileRotator} history, migrating it
+ * to {@link TrafficStats#UID_REMOVED}.
+ */
+ public void removeUidLocked(int uid) {
+ try {
+ // process all existing data to migrate uid
+ mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uid));
+ } catch (IOException e) {
+ Log.wtf(TAG, "problem removing UID " + uid, e);
+ }
+
+ // clear UID from current stats snapshot
+ if (mLastSnapshot != null) {
+ mLastSnapshot = mLastSnapshot.withoutUid(uid);
+ }
+ }
+
+ /**
+ * Rewriter that will combine current {@link NetworkStatsCollection} values
+ * with anything read from disk, and write combined set to disk. Clears the
+ * original {@link NetworkStatsCollection} when finished writing.
+ */
+ private static class CombiningRewriter implements FileRotator.Rewriter {
+ private final NetworkStatsCollection mCollection;
+
+ public CombiningRewriter(NetworkStatsCollection collection) {
+ mCollection = checkNotNull(collection, "missing NetworkStatsCollection");
+ }
+
+ /** {@inheritDoc} */
+ public void reset() {
+ // ignored
+ }
+
+ /** {@inheritDoc} */
+ public void read(InputStream in) throws IOException {
+ mCollection.read(in);
+ }
+
+ /** {@inheritDoc} */
+ public boolean shouldWrite() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ public void write(OutputStream out) throws IOException {
+ mCollection.write(new DataOutputStream(out));
+ mCollection.reset();
+ }
+ }
+
+ /**
+ * Rewriter that will remove any {@link NetworkStatsHistory} attributed to
+ * the requested UID, only writing data back when modified.
+ */
+ public static class RemoveUidRewriter implements FileRotator.Rewriter {
+ private final NetworkStatsCollection mTemp;
+ private final int mUid;
+
+ public RemoveUidRewriter(long bucketDuration, int uid) {
+ mTemp = new NetworkStatsCollection(bucketDuration);
+ mUid = uid;
+ }
+
+ /** {@inheritDoc} */
+ public void reset() {
+ mTemp.reset();
+ }
+
+ /** {@inheritDoc} */
+ public void read(InputStream in) throws IOException {
+ mTemp.read(in);
+ mTemp.clearDirty();
+ mTemp.removeUid(mUid);
+ }
+
+ /** {@inheritDoc} */
+ public boolean shouldWrite() {
+ return mTemp.isDirty();
+ }
+
+ /** {@inheritDoc} */
+ public void write(OutputStream out) throws IOException {
+ mTemp.write(new DataOutputStream(out));
+ }
+ }
+
+ public void importLegacyNetworkLocked(File file) throws IOException {
+ // legacy file still exists; start empty to avoid double importing
+ mRotator.deleteAll();
+
+ final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
+ collection.readLegacyNetwork(file);
+
+ final long startMillis = collection.getStartMillis();
+ final long endMillis = collection.getEndMillis();
+
+ if (!collection.isEmpty()) {
+ // process legacy data, creating active file at starting time, then
+ // using end time to possibly trigger rotation.
+ mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
+ mRotator.maybeRotate(endMillis);
+ }
+ }
+
+ public void importLegacyUidLocked(File file) throws IOException {
+ // legacy file still exists; start empty to avoid double importing
+ mRotator.deleteAll();
+
+ final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
+ collection.readLegacyUid(file, mOnlyTags);
+
+ final long startMillis = collection.getStartMillis();
+ final long endMillis = collection.getEndMillis();
+
+ if (!collection.isEmpty()) {
+ // process legacy data, creating active file at starting time, then
+ // using end time to possibly trigger rotation.
+ mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
+ mRotator.maybeRotate(endMillis);
+ }
+ }
+
+ public void dumpLocked(IndentingPrintWriter pw, boolean fullHistory) {
+ pw.print("Pending bytes: "); pw.println(mPending.getTotalBytes());
+ if (fullHistory) {
+ pw.println("Complete history:");
+ getOrLoadCompleteLocked().dump(pw);
+ } else {
+ pw.println("History since boot:");
+ mSinceBoot.dump(pw);
+ }
+ }
+}
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index eeb7fec..c9b79e8 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -34,14 +34,18 @@
import static android.net.NetworkStats.UID_ALL;
import static android.net.NetworkTemplate.buildTemplateMobileAll;
import static android.net.NetworkTemplate.buildTemplateWifi;
-import static android.net.TrafficStats.UID_REMOVED;
-import static android.provider.Settings.Secure.NETSTATS_NETWORK_BUCKET_DURATION;
-import static android.provider.Settings.Secure.NETSTATS_NETWORK_MAX_HISTORY;
-import static android.provider.Settings.Secure.NETSTATS_PERSIST_THRESHOLD;
+import static android.provider.Settings.Secure.NETSTATS_DEV_BUCKET_DURATION;
+import static android.provider.Settings.Secure.NETSTATS_DEV_DELETE_AGE;
+import static android.provider.Settings.Secure.NETSTATS_DEV_PERSIST_BYTES;
+import static android.provider.Settings.Secure.NETSTATS_DEV_ROTATE_AGE;
+import static android.provider.Settings.Secure.NETSTATS_GLOBAL_ALERT_BYTES;
import static android.provider.Settings.Secure.NETSTATS_POLL_INTERVAL;
-import static android.provider.Settings.Secure.NETSTATS_TAG_MAX_HISTORY;
+import static android.provider.Settings.Secure.NETSTATS_SAMPLE_ENABLED;
+import static android.provider.Settings.Secure.NETSTATS_TIME_CACHE_MAX_AGE;
import static android.provider.Settings.Secure.NETSTATS_UID_BUCKET_DURATION;
-import static android.provider.Settings.Secure.NETSTATS_UID_MAX_HISTORY;
+import static android.provider.Settings.Secure.NETSTATS_UID_DELETE_AGE;
+import static android.provider.Settings.Secure.NETSTATS_UID_PERSIST_BYTES;
+import static android.provider.Settings.Secure.NETSTATS_UID_ROTATE_AGE;
import static android.telephony.PhoneStateListener.LISTEN_DATA_CONNECTION_STATE;
import static android.telephony.PhoneStateListener.LISTEN_NONE;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
@@ -61,12 +65,10 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.net.IConnectivityManager;
import android.net.INetworkManagementEventObserver;
import android.net.INetworkStatsService;
+import android.net.LinkProperties;
import android.net.NetworkIdentity;
import android.net.NetworkInfo;
import android.net.NetworkState;
@@ -74,6 +76,7 @@
import android.net.NetworkStats.NonMonotonicObserver;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
+import android.net.TrafficStats;
import android.os.Binder;
import android.os.DropBoxManager;
import android.os.Environment;
@@ -94,32 +97,18 @@
import android.util.SparseIntArray;
import android.util.TrustedTime;
-import com.android.internal.os.AtomicFile;
-import com.android.internal.util.Objects;
+import com.android.internal.util.FileRotator;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.server.EventLogTags;
import com.android.server.connectivity.Tethering;
-import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
-import com.google.android.collect.Sets;
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
-import java.net.ProtocolException;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Random;
-
-import libcore.io.IoUtils;
/**
* Collect and persist detailed network statistics, and provide this data to
@@ -127,16 +116,8 @@
*/
public class NetworkStatsService extends INetworkStatsService.Stub {
private static final String TAG = "NetworkStats";
- private static final boolean LOGD = false;
- private static final boolean LOGV = false;
-
- /** File header magic number: "ANET" */
- private static final int FILE_MAGIC = 0x414E4554;
- private static final int VERSION_NETWORK_INIT = 1;
- private static final int VERSION_UID_INIT = 1;
- private static final int VERSION_UID_WITH_IDENT = 2;
- private static final int VERSION_UID_WITH_TAG = 3;
- private static final int VERSION_UID_WITH_SET = 4;
+ private static final boolean LOGD = true;
+ private static final boolean LOGV = true;
private static final int MSG_PERFORM_POLL = 1;
private static final int MSG_UPDATE_IFACES = 2;
@@ -147,9 +128,6 @@
private static final int FLAG_PERSIST_ALL = FLAG_PERSIST_NETWORK | FLAG_PERSIST_UID;
private static final int FLAG_PERSIST_FORCE = 0x100;
- /** Sample recent usage after each poll event. */
- private static final boolean ENABLE_SAMPLE_AFTER_POLL = true;
-
private static final String TAG_NETSTATS_ERROR = "netstats_error";
private final Context mContext;
@@ -159,10 +137,12 @@
private final TelephonyManager mTeleManager;
private final NetworkStatsSettings mSettings;
+ private final File mSystemDir;
+ private final File mBaseDir;
+
private final PowerManager.WakeLock mWakeLock;
private IConnectivityManager mConnManager;
- private DropBoxManager mDropBox;
// @VisibleForTesting
public static final String ACTION_NETWORK_STATS_POLL =
@@ -172,71 +152,76 @@
private PendingIntent mPollIntent;
- // TODO: trim empty history objects entirely
-
private static final long KB_IN_BYTES = 1024;
private static final long MB_IN_BYTES = 1024 * KB_IN_BYTES;
private static final long GB_IN_BYTES = 1024 * MB_IN_BYTES;
+ private static final String PREFIX_DEV = "dev";
+ private static final String PREFIX_UID = "uid";
+ private static final String PREFIX_UID_TAG = "uid_tag";
+
/**
* Settings that can be changed externally.
*/
public interface NetworkStatsSettings {
public long getPollInterval();
- public long getPersistThreshold();
- public long getNetworkBucketDuration();
- public long getNetworkMaxHistory();
- public long getUidBucketDuration();
- public long getUidMaxHistory();
- public long getTagMaxHistory();
public long getTimeCacheMaxAge();
+ public long getGlobalAlertBytes();
+ public boolean getSampleEnabled();
+
+ public static class Config {
+ public final long bucketDuration;
+ public final long persistBytes;
+ public final long rotateAgeMillis;
+ public final long deleteAgeMillis;
+
+ public Config(long bucketDuration, long persistBytes, long rotateAgeMillis,
+ long deleteAgeMillis) {
+ this.bucketDuration = bucketDuration;
+ this.persistBytes = persistBytes;
+ this.rotateAgeMillis = rotateAgeMillis;
+ this.deleteAgeMillis = deleteAgeMillis;
+ }
+ }
+
+ public Config getDevConfig();
+ public Config getUidConfig();
+ public Config getUidTagConfig();
}
private final Object mStatsLock = new Object();
/** Set of currently active ifaces. */
private HashMap<String, NetworkIdentitySet> mActiveIfaces = Maps.newHashMap();
- /** Set of historical {@code dev} stats for known networks. */
- private HashMap<NetworkIdentitySet, NetworkStatsHistory> mNetworkDevStats = Maps.newHashMap();
- /** Set of historical {@code xtables} stats for known networks. */
- private HashMap<NetworkIdentitySet, NetworkStatsHistory> mNetworkXtStats = Maps.newHashMap();
- /** Set of historical {@code xtables} stats for known UIDs. */
- private HashMap<UidStatsKey, NetworkStatsHistory> mUidStats = Maps.newHashMap();
+ /** Current default active iface. */
+ private String mActiveIface;
- /** Flag if {@link #mNetworkDevStats} have been loaded from disk. */
- private boolean mNetworkStatsLoaded = false;
- /** Flag if {@link #mUidStats} have been loaded from disk. */
- private boolean mUidStatsLoaded = false;
+ private final DropBoxNonMonotonicObserver mNonMonotonicObserver =
+ new DropBoxNonMonotonicObserver();
- private NetworkStats mLastPollNetworkDevSnapshot;
- private NetworkStats mLastPollNetworkXtSnapshot;
- private NetworkStats mLastPollUidSnapshot;
- private NetworkStats mLastPollOperationsSnapshot;
+ private NetworkStatsRecorder mDevRecorder;
+ private NetworkStatsRecorder mUidRecorder;
+ private NetworkStatsRecorder mUidTagRecorder;
- private NetworkStats mLastPersistNetworkDevSnapshot;
- private NetworkStats mLastPersistNetworkXtSnapshot;
- private NetworkStats mLastPersistUidSnapshot;
+ /** Cached {@link #mDevRecorder} stats. */
+ private NetworkStatsCollection mDevStatsCached;
/** Current counter sets for each UID. */
private SparseIntArray mActiveUidCounterSet = new SparseIntArray();
/** Data layer operation counters for splicing into other structures. */
- private NetworkStats mOperations = new NetworkStats(0L, 10);
+ private NetworkStats mUidOperations = new NetworkStats(0L, 10);
private final HandlerThread mHandlerThread;
private final Handler mHandler;
- private final AtomicFile mNetworkDevFile;
- private final AtomicFile mNetworkXtFile;
- private final AtomicFile mUidFile;
-
public NetworkStatsService(
Context context, INetworkManagementService networkManager, IAlarmManager alarmManager) {
this(context, networkManager, alarmManager, NtpTrustedTime.getInstance(context),
- getSystemDir(), new DefaultNetworkStatsSettings(context));
+ getDefaultSystemDir(), new DefaultNetworkStatsSettings(context));
}
- private static File getSystemDir() {
+ private static File getDefaultSystemDir() {
return new File(Environment.getDataDirectory(), "system");
}
@@ -258,9 +243,9 @@
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper(), mHandlerCallback);
- mNetworkDevFile = new AtomicFile(new File(systemDir, "netstats.bin"));
- mNetworkXtFile = new AtomicFile(new File(systemDir, "netstats_xt.bin"));
- mUidFile = new AtomicFile(new File(systemDir, "netstats_uid.bin"));
+ mSystemDir = checkNotNull(systemDir);
+ mBaseDir = new File(systemDir, "netstats");
+ mBaseDir.mkdirs();
}
public void bindConnectivityManager(IConnectivityManager connManager) {
@@ -273,17 +258,22 @@
return;
}
- synchronized (mStatsLock) {
- // read historical network stats from disk, since policy service
- // might need them right away. we delay loading detailed UID stats
- // until actually needed.
- readNetworkDevStatsLocked();
- readNetworkXtStatsLocked();
- mNetworkStatsLoaded = true;
- }
+ // create data recorders along with historical rotators
+ mDevRecorder = buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false);
+ mUidRecorder = buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false);
+ mUidTagRecorder = buildRecorder(PREFIX_UID_TAG, mSettings.getUidTagConfig(), true);
- // bootstrap initial stats to prevent double-counting later
- bootstrapStats();
+ synchronized (mStatsLock) {
+ // upgrade any legacy stats, migrating them to rotated files
+ maybeUpgradeLegacyStatsLocked();
+
+ // read historical network stats from disk, since policy service
+ // might need them right away.
+ mDevStatsCached = mDevRecorder.getOrLoadCompleteLocked();
+
+ // bootstrap initial stats to prevent double-counting later
+ bootstrapStatsLocked();
+ }
// watch for network interfaces to be claimed
final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION_IMMEDIATE);
@@ -317,8 +307,14 @@
registerPollAlarmLocked();
registerGlobalAlert();
+ }
- mDropBox = (DropBoxManager) mContext.getSystemService(Context.DROPBOX_SERVICE);
+ private NetworkStatsRecorder buildRecorder(
+ String prefix, NetworkStatsSettings.Config config, boolean includeTags) {
+ return new NetworkStatsRecorder(
+ new FileRotator(mBaseDir, prefix, config.rotateAgeMillis, config.deleteAgeMillis),
+ mNonMonotonicObserver, prefix, config.bucketDuration, config.persistBytes,
+ includeTags);
}
private void shutdownLocked() {
@@ -330,18 +326,44 @@
mTeleManager.listen(mPhoneListener, LISTEN_NONE);
- if (mNetworkStatsLoaded) {
- writeNetworkDevStatsLocked();
- writeNetworkXtStatsLocked();
+ final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
+ : System.currentTimeMillis();
+
+ // persist any pending stats
+ mDevRecorder.forcePersistLocked(currentTime);
+ mUidRecorder.forcePersistLocked(currentTime);
+ mUidTagRecorder.forcePersistLocked(currentTime);
+
+ mDevRecorder = null;
+ mUidRecorder = null;
+ mUidTagRecorder = null;
+
+ mDevStatsCached = null;
+ }
+
+ private void maybeUpgradeLegacyStatsLocked() {
+ File file;
+ try {
+ file = new File(mSystemDir, "netstats.bin");
+ if (file.exists()) {
+ mDevRecorder.importLegacyNetworkLocked(file);
+ file.delete();
+ }
+
+ file = new File(mSystemDir, "netstats_xt.bin");
+ if (file.exists()) {
+ file.delete();
+ }
+
+ file = new File(mSystemDir, "netstats_uid.bin");
+ if (file.exists()) {
+ mUidRecorder.importLegacyUidLocked(file);
+ mUidTagRecorder.importLegacyUidLocked(file);
+ file.delete();
+ }
+ } catch (IOException e) {
+ Log.wtf(TAG, "problem during legacy upgrade", e);
}
- if (mUidStatsLoaded) {
- writeUidStatsLocked();
- }
- mNetworkDevStats.clear();
- mNetworkXtStats.clear();
- mUidStats.clear();
- mNetworkStatsLoaded = false;
- mUidStatsLoaded = false;
}
/**
@@ -372,7 +394,7 @@
*/
private void registerGlobalAlert() {
try {
- final long alertBytes = mSettings.getPersistThreshold();
+ final long alertBytes = mSettings.getGlobalAlertBytes();
mNetworkManager.setGlobalAlert(alertBytes);
} catch (IllegalStateException e) {
Slog.w(TAG, "problem registering for global alert: " + e);
@@ -383,161 +405,39 @@
@Override
public NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template, int fields) {
- mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
- return getHistoryForNetworkDev(template, fields);
+ return mDevStatsCached.getHistory(template, UID_ALL, SET_ALL, TAG_NONE, fields);
}
- private NetworkStatsHistory getHistoryForNetworkDev(NetworkTemplate template, int fields) {
- return getHistoryForNetwork(template, fields, mNetworkDevStats);
- }
-
- private NetworkStatsHistory getHistoryForNetworkXt(NetworkTemplate template, int fields) {
- return getHistoryForNetwork(template, fields, mNetworkXtStats);
- }
-
- private NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template, int fields,
- HashMap<NetworkIdentitySet, NetworkStatsHistory> source) {
- synchronized (mStatsLock) {
- // combine all interfaces that match template
- final NetworkStatsHistory combined = new NetworkStatsHistory(
- mSettings.getNetworkBucketDuration(), estimateNetworkBuckets(), fields);
- for (NetworkIdentitySet ident : source.keySet()) {
- if (templateMatches(template, ident)) {
- final NetworkStatsHistory history = source.get(ident);
- if (history != null) {
- combined.recordEntireHistory(history);
- }
- }
- }
- return combined;
- }
+ @Override
+ public NetworkStats getSummaryForNetwork(NetworkTemplate template, long start, long end) {
+ return mDevStatsCached.getSummary(template, start, end);
}
@Override
public NetworkStatsHistory getHistoryForUid(
NetworkTemplate template, int uid, int set, int tag, int fields) {
- mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
-
- synchronized (mStatsLock) {
- ensureUidStatsLoadedLocked();
-
- // combine all interfaces that match template
- final NetworkStatsHistory combined = new NetworkStatsHistory(
- mSettings.getUidBucketDuration(), estimateUidBuckets(), fields);
- for (UidStatsKey key : mUidStats.keySet()) {
- final boolean setMatches = set == SET_ALL || key.set == set;
- if (templateMatches(template, key.ident) && key.uid == uid && setMatches
- && key.tag == tag) {
- final NetworkStatsHistory history = mUidStats.get(key);
- combined.recordEntireHistory(history);
- }
- }
-
- return combined;
+ // TODO: transition to stats sessions to avoid WeakReferences
+ if (tag == TAG_NONE) {
+ return mUidRecorder.getOrLoadCompleteLocked().getHistory(
+ template, uid, set, tag, fields);
+ } else {
+ return mUidTagRecorder.getOrLoadCompleteLocked().getHistory(
+ template, uid, set, tag, fields);
}
}
@Override
- public NetworkStats getSummaryForNetwork(NetworkTemplate template, long start, long end) {
- mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
- return getSummaryForNetworkDev(template, start, end);
- }
-
- private NetworkStats getSummaryForNetworkDev(NetworkTemplate template, long start, long end) {
- return getSummaryForNetwork(template, start, end, mNetworkDevStats);
- }
-
- private NetworkStats getSummaryForNetworkXt(NetworkTemplate template, long start, long end) {
- return getSummaryForNetwork(template, start, end, mNetworkXtStats);
- }
-
- private NetworkStats getSummaryForNetwork(NetworkTemplate template, long start, long end,
- HashMap<NetworkIdentitySet, NetworkStatsHistory> source) {
- synchronized (mStatsLock) {
- // use system clock to be externally consistent
- final long now = System.currentTimeMillis();
-
- final NetworkStats stats = new NetworkStats(end - start, 1);
- final NetworkStats.Entry entry = new NetworkStats.Entry();
- NetworkStatsHistory.Entry historyEntry = null;
-
- // combine total from all interfaces that match template
- for (NetworkIdentitySet ident : source.keySet()) {
- if (templateMatches(template, ident)) {
- final NetworkStatsHistory history = source.get(ident);
- historyEntry = history.getValues(start, end, now, historyEntry);
-
- entry.iface = IFACE_ALL;
- entry.uid = UID_ALL;
- entry.tag = TAG_NONE;
- entry.rxBytes = historyEntry.rxBytes;
- entry.rxPackets = historyEntry.rxPackets;
- entry.txBytes = historyEntry.txBytes;
- entry.txPackets = historyEntry.txPackets;
-
- stats.combineValues(entry);
- }
- }
-
- return stats;
- }
- }
-
- private long getHistoryStartLocked(
- NetworkTemplate template, HashMap<NetworkIdentitySet, NetworkStatsHistory> source) {
- long start = Long.MAX_VALUE;
- for (NetworkIdentitySet ident : source.keySet()) {
- if (templateMatches(template, ident)) {
- final NetworkStatsHistory history = source.get(ident);
- start = Math.min(start, history.getStart());
- }
- }
- return start;
- }
-
- @Override
public NetworkStats getSummaryForAllUid(
NetworkTemplate template, long start, long end, boolean includeTags) {
- mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
-
- synchronized (mStatsLock) {
- ensureUidStatsLoadedLocked();
-
- // use system clock to be externally consistent
- final long now = System.currentTimeMillis();
-
- final NetworkStats stats = new NetworkStats(end - start, 24);
- final NetworkStats.Entry entry = new NetworkStats.Entry();
- NetworkStatsHistory.Entry historyEntry = null;
-
- for (UidStatsKey key : mUidStats.keySet()) {
- if (templateMatches(template, key.ident)) {
- // always include summary under TAG_NONE, and include
- // other tags when requested.
- if (key.tag == TAG_NONE || includeTags) {
- final NetworkStatsHistory history = mUidStats.get(key);
- historyEntry = history.getValues(start, end, now, historyEntry);
-
- entry.iface = IFACE_ALL;
- entry.uid = key.uid;
- entry.set = key.set;
- entry.tag = key.tag;
- entry.rxBytes = historyEntry.rxBytes;
- entry.rxPackets = historyEntry.rxPackets;
- entry.txBytes = historyEntry.txBytes;
- entry.txPackets = historyEntry.txPackets;
- entry.operations = historyEntry.operations;
-
- if (entry.rxBytes > 0 || entry.rxPackets > 0 || entry.txBytes > 0
- || entry.txPackets > 0 || entry.operations > 0) {
- stats.combineValues(entry);
- }
- }
- }
- }
-
- return stats;
+ // TODO: transition to stats sessions to avoid WeakReferences
+ final NetworkStats stats = mUidRecorder.getOrLoadCompleteLocked().getSummary(
+ template, start, end);
+ if (includeTags) {
+ final NetworkStats tagStats = mUidTagRecorder.getOrLoadCompleteLocked().getSummary(
+ template, start, end);
+ stats.combineAllValues(tagStats);
}
+ return stats;
}
@Override
@@ -567,7 +467,7 @@
}
// splice in operation counts
- dataLayer.spliceOperationsFrom(mOperations);
+ dataLayer.spliceOperationsFrom(mUidOperations);
return dataLayer;
}
@@ -586,8 +486,10 @@
synchronized (mStatsLock) {
final int set = mActiveUidCounterSet.get(uid, SET_DEFAULT);
- mOperations.combineValues(IFACE_ALL, uid, set, tag, 0L, 0L, 0L, 0L, operationCount);
- mOperations.combineValues(IFACE_ALL, uid, set, TAG_NONE, 0L, 0L, 0L, 0L, operationCount);
+ mUidOperations.combineValues(
+ mActiveIface, uid, set, tag, 0L, 0L, 0L, 0L, operationCount);
+ mUidOperations.combineValues(
+ mActiveIface, uid, set, TAG_NONE, 0L, 0L, 0L, 0L, operationCount);
}
}
@@ -755,13 +657,17 @@
performPollLocked(FLAG_PERSIST_NETWORK);
final NetworkState[] states;
+ final LinkProperties activeLink;
try {
states = mConnManager.getAllNetworkState();
+ activeLink = mConnManager.getActiveLinkProperties();
} catch (RemoteException e) {
// ignored; service lives in system_server
return;
}
+ mActiveIface = activeLink != null ? activeLink.getInterfaceName() : null;
+
// rebuild active interfaces based on connected networks
mActiveIfaces.clear();
@@ -785,12 +691,20 @@
* Bootstrap initial stats snapshot, usually during {@link #systemReady()}
* so we have baseline values without double-counting.
*/
- private void bootstrapStats() {
+ private void bootstrapStatsLocked() {
+ final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
+ : System.currentTimeMillis();
+
try {
- mLastPollUidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL);
- mLastPollNetworkDevSnapshot = mNetworkManager.getNetworkStatsSummary();
- mLastPollNetworkXtSnapshot = computeNetworkXtSnapshotFromUid(mLastPollUidSnapshot);
- mLastPollOperationsSnapshot = new NetworkStats(0L, 0);
+ // snapshot and record current counters; read UID stats first to
+ // avoid overcounting dev stats.
+ final NetworkStats uidSnapshot = getNetworkStatsUidDetail();
+ final NetworkStats devSnapshot = getNetworkStatsSummary();
+
+ mDevRecorder.recordSnapshotLocked(devSnapshot, mActiveIfaces, currentTime);
+ mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime);
+ mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime);
+
} catch (IllegalStateException e) {
Slog.w(TAG, "problem reading network stats: " + e);
} catch (RemoteException e) {
@@ -830,27 +744,16 @@
// TODO: consider marking "untrusted" times in historical stats
final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
: System.currentTimeMillis();
- final long threshold = mSettings.getPersistThreshold();
- final NetworkStats uidSnapshot;
- final NetworkStats networkXtSnapshot;
- final NetworkStats networkDevSnapshot;
try {
- // collect any tethering stats
- final NetworkStats tetherSnapshot = getNetworkStatsTethering();
+ // snapshot and record current counters; read UID stats first to
+ // avoid overcounting dev stats.
+ final NetworkStats uidSnapshot = getNetworkStatsUidDetail();
+ final NetworkStats devSnapshot = getNetworkStatsSummary();
- // record uid stats, folding in tethering stats
- uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL);
- uidSnapshot.combineAllValues(tetherSnapshot);
- performUidPollLocked(uidSnapshot, currentTime);
-
- // record dev network stats
- networkDevSnapshot = mNetworkManager.getNetworkStatsSummary();
- performNetworkDevPollLocked(networkDevSnapshot, currentTime);
-
- // record xt network stats
- networkXtSnapshot = computeNetworkXtSnapshotFromUid(uidSnapshot);
- performNetworkXtPollLocked(networkXtSnapshot, currentTime);
+ mDevRecorder.recordSnapshotLocked(devSnapshot, mActiveIfaces, currentTime);
+ mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime);
+ mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime);
} catch (IllegalStateException e) {
Log.wtf(TAG, "problem reading network stats", e);
@@ -860,26 +763,19 @@
return;
}
- // persist when enough network data has occurred
- final long persistNetworkDevDelta = computeStatsDelta(
- mLastPersistNetworkDevSnapshot, networkDevSnapshot, true, "devp").getTotalBytes();
- final long persistNetworkXtDelta = computeStatsDelta(
- mLastPersistNetworkXtSnapshot, networkXtSnapshot, true, "xtp").getTotalBytes();
- final boolean networkOverThreshold = persistNetworkDevDelta > threshold
- || persistNetworkXtDelta > threshold;
- if (persistForce || (persistNetwork && networkOverThreshold)) {
- writeNetworkDevStatsLocked();
- writeNetworkXtStatsLocked();
- mLastPersistNetworkDevSnapshot = networkDevSnapshot;
- mLastPersistNetworkXtSnapshot = networkXtSnapshot;
- }
-
- // persist when enough uid data has occurred
- final long persistUidDelta = computeStatsDelta(
- mLastPersistUidSnapshot, uidSnapshot, true, "uidp").getTotalBytes();
- if (persistForce || (persistUid && persistUidDelta > threshold)) {
- writeUidStatsLocked();
- mLastPersistUidSnapshot = uidSnapshot;
+ // persist any pending data depending on requested flags
+ if (persistForce) {
+ mDevRecorder.forcePersistLocked(currentTime);
+ mUidRecorder.forcePersistLocked(currentTime);
+ mUidTagRecorder.forcePersistLocked(currentTime);
+ } else {
+ if (persistNetwork) {
+ mDevRecorder.maybePersistLocked(currentTime);
+ }
+ if (persistUid) {
+ mUidRecorder.maybePersistLocked(currentTime);
+ mUidTagRecorder.maybePersistLocked(currentTime);
+ }
}
if (LOGV) {
@@ -887,9 +783,9 @@
Slog.v(TAG, "performPollLocked() took " + duration + "ms");
}
- if (ENABLE_SAMPLE_AFTER_POLL) {
+ if (mSettings.getSampleEnabled()) {
// sample stats after each full poll
- performSample();
+ performSampleLocked();
}
// finally, dispatch updated event to any listeners
@@ -899,511 +795,58 @@
}
/**
- * Update {@link #mNetworkDevStats} historical usage.
- */
- private void performNetworkDevPollLocked(NetworkStats networkDevSnapshot, long currentTime) {
- final HashSet<String> unknownIface = Sets.newHashSet();
-
- final NetworkStats delta = computeStatsDelta(
- mLastPollNetworkDevSnapshot, networkDevSnapshot, false, "dev");
- final long timeStart = currentTime - delta.getElapsedRealtime();
-
- NetworkStats.Entry entry = null;
- for (int i = 0; i < delta.size(); i++) {
- entry = delta.getValues(i, entry);
- final NetworkIdentitySet ident = mActiveIfaces.get(entry.iface);
- if (ident == null) {
- unknownIface.add(entry.iface);
- continue;
- }
-
- final NetworkStatsHistory history = findOrCreateNetworkDevStatsLocked(ident);
- history.recordData(timeStart, currentTime, entry);
- }
-
- mLastPollNetworkDevSnapshot = networkDevSnapshot;
-
- if (LOGD && unknownIface.size() > 0) {
- Slog.w(TAG, "unknown dev interfaces " + unknownIface + ", ignoring those stats");
- }
- }
-
- /**
- * Update {@link #mNetworkXtStats} historical usage.
- */
- private void performNetworkXtPollLocked(NetworkStats networkXtSnapshot, long currentTime) {
- final HashSet<String> unknownIface = Sets.newHashSet();
-
- final NetworkStats delta = computeStatsDelta(
- mLastPollNetworkXtSnapshot, networkXtSnapshot, false, "xt");
- final long timeStart = currentTime - delta.getElapsedRealtime();
-
- NetworkStats.Entry entry = null;
- for (int i = 0; i < delta.size(); i++) {
- entry = delta.getValues(i, entry);
- final NetworkIdentitySet ident = mActiveIfaces.get(entry.iface);
- if (ident == null) {
- unknownIface.add(entry.iface);
- continue;
- }
-
- final NetworkStatsHistory history = findOrCreateNetworkXtStatsLocked(ident);
- history.recordData(timeStart, currentTime, entry);
- }
-
- mLastPollNetworkXtSnapshot = networkXtSnapshot;
-
- if (LOGD && unknownIface.size() > 0) {
- Slog.w(TAG, "unknown xt interfaces " + unknownIface + ", ignoring those stats");
- }
- }
-
- /**
- * Update {@link #mUidStats} historical usage.
- */
- private void performUidPollLocked(NetworkStats uidSnapshot, long currentTime) {
- ensureUidStatsLoadedLocked();
-
- final NetworkStats delta = computeStatsDelta(
- mLastPollUidSnapshot, uidSnapshot, false, "uid");
- final NetworkStats operationsDelta = computeStatsDelta(
- mLastPollOperationsSnapshot, mOperations, false, "uidop");
- final long timeStart = currentTime - delta.getElapsedRealtime();
-
- NetworkStats.Entry entry = null;
- NetworkStats.Entry operationsEntry = null;
- for (int i = 0; i < delta.size(); i++) {
- entry = delta.getValues(i, entry);
- final NetworkIdentitySet ident = mActiveIfaces.get(entry.iface);
- if (ident == null) {
- if (entry.rxBytes > 0 || entry.rxPackets > 0 || entry.txBytes > 0
- || entry.txPackets > 0) {
- Log.w(TAG, "dropping UID delta from unknown iface: " + entry);
- }
- continue;
- }
-
- // splice in operation counts since last poll
- final int j = operationsDelta.findIndex(IFACE_ALL, entry.uid, entry.set, entry.tag);
- if (j != -1) {
- operationsEntry = operationsDelta.getValues(j, operationsEntry);
- entry.operations = operationsEntry.operations;
- }
-
- final NetworkStatsHistory history = findOrCreateUidStatsLocked(
- ident, entry.uid, entry.set, entry.tag);
- history.recordData(timeStart, currentTime, entry);
- }
-
- mLastPollUidSnapshot = uidSnapshot;
- mLastPollOperationsSnapshot = mOperations.clone();
- }
-
- /**
* Sample recent statistics summary into {@link EventLog}.
*/
- private void performSample() {
- final long largestBucketSize = Math.max(
- mSettings.getNetworkBucketDuration(), mSettings.getUidBucketDuration());
-
- // take sample as atomic buckets
- final long now = mTime.hasCache() ? mTime.currentTimeMillis() : System.currentTimeMillis();
- final long end = now - (now % largestBucketSize) + largestBucketSize;
- final long start = end - largestBucketSize;
-
+ private void performSampleLocked() {
+ // TODO: migrate trustedtime fixes to separate binary log events
final long trustedTime = mTime.hasCache() ? mTime.currentTimeMillis() : -1;
- long devHistoryStart = Long.MAX_VALUE;
- NetworkTemplate template = null;
- NetworkStats.Entry devTotal = null;
- NetworkStats.Entry xtTotal = null;
- NetworkStats.Entry uidTotal = null;
+ NetworkTemplate template;
+ NetworkStats.Entry devTotal;
+ NetworkStats.Entry xtTotal;
+ NetworkStats.Entry uidTotal;
// collect mobile sample
template = buildTemplateMobileAll(getActiveSubscriberId(mContext));
- devTotal = getSummaryForNetworkDev(template, start, end).getTotal(devTotal);
- devHistoryStart = getHistoryStartLocked(template, mNetworkDevStats);
- xtTotal = getSummaryForNetworkXt(template, start, end).getTotal(xtTotal);
- uidTotal = getSummaryForAllUid(template, start, end, false).getTotal(uidTotal);
+ devTotal = mDevRecorder.getTotalSinceBootLocked(template);
+ xtTotal = new NetworkStats.Entry();
+ uidTotal = mUidRecorder.getTotalSinceBootLocked(template);
EventLogTags.writeNetstatsMobileSample(
devTotal.rxBytes, devTotal.rxPackets, devTotal.txBytes, devTotal.txPackets,
xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets,
uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets,
- trustedTime, devHistoryStart);
+ trustedTime);
// collect wifi sample
template = buildTemplateWifi();
- devTotal = getSummaryForNetworkDev(template, start, end).getTotal(devTotal);
- devHistoryStart = getHistoryStartLocked(template, mNetworkDevStats);
- xtTotal = getSummaryForNetworkXt(template, start, end).getTotal(xtTotal);
- uidTotal = getSummaryForAllUid(template, start, end, false).getTotal(uidTotal);
+ devTotal = mDevRecorder.getTotalSinceBootLocked(template);
+ xtTotal = new NetworkStats.Entry();
+ uidTotal = mUidRecorder.getTotalSinceBootLocked(template);
+
EventLogTags.writeNetstatsWifiSample(
devTotal.rxBytes, devTotal.rxPackets, devTotal.txBytes, devTotal.txPackets,
xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets,
uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets,
- trustedTime, devHistoryStart);
+ trustedTime);
}
/**
- * Clean up {@link #mUidStats} after UID is removed.
+ * Clean up {@link #mUidRecorder} after UID is removed.
*/
private void removeUidLocked(int uid) {
- ensureUidStatsLoadedLocked();
-
// perform one last poll before removing
performPollLocked(FLAG_PERSIST_ALL);
- final ArrayList<UidStatsKey> knownKeys = Lists.newArrayList();
- knownKeys.addAll(mUidStats.keySet());
-
- // migrate all UID stats into special "removed" bucket
- for (UidStatsKey key : knownKeys) {
- if (key.uid == uid) {
- // only migrate combined TAG_NONE history
- if (key.tag == TAG_NONE) {
- final NetworkStatsHistory uidHistory = mUidStats.get(key);
- final NetworkStatsHistory removedHistory = findOrCreateUidStatsLocked(
- key.ident, UID_REMOVED, SET_DEFAULT, TAG_NONE);
- removedHistory.recordEntireHistory(uidHistory);
- }
- mUidStats.remove(key);
- }
- }
-
- // clear UID from current stats snapshot
- if (mLastPollUidSnapshot != null) {
- mLastPollUidSnapshot = mLastPollUidSnapshot.withoutUid(uid);
- mLastPollNetworkXtSnapshot = computeNetworkXtSnapshotFromUid(mLastPollUidSnapshot);
- }
+ mUidRecorder.removeUidLocked(uid);
+ mUidTagRecorder.removeUidLocked(uid);
// clear kernel stats associated with UID
resetKernelUidStats(uid);
-
- // since this was radical rewrite, push to disk
- writeUidStatsLocked();
- }
-
- private NetworkStatsHistory findOrCreateNetworkXtStatsLocked(NetworkIdentitySet ident) {
- return findOrCreateNetworkStatsLocked(ident, mNetworkXtStats);
- }
-
- private NetworkStatsHistory findOrCreateNetworkDevStatsLocked(NetworkIdentitySet ident) {
- return findOrCreateNetworkStatsLocked(ident, mNetworkDevStats);
- }
-
- private NetworkStatsHistory findOrCreateNetworkStatsLocked(
- NetworkIdentitySet ident, HashMap<NetworkIdentitySet, NetworkStatsHistory> source) {
- final NetworkStatsHistory existing = source.get(ident);
-
- // update when no existing, or when bucket duration changed
- final long bucketDuration = mSettings.getNetworkBucketDuration();
- NetworkStatsHistory updated = null;
- if (existing == null) {
- updated = new NetworkStatsHistory(bucketDuration, 10);
- } else if (existing.getBucketDuration() != bucketDuration) {
- updated = new NetworkStatsHistory(
- bucketDuration, estimateResizeBuckets(existing, bucketDuration));
- updated.recordEntireHistory(existing);
- }
-
- if (updated != null) {
- source.put(ident, updated);
- return updated;
- } else {
- return existing;
- }
- }
-
- private NetworkStatsHistory findOrCreateUidStatsLocked(
- NetworkIdentitySet ident, int uid, int set, int tag) {
- ensureUidStatsLoadedLocked();
-
- final UidStatsKey key = new UidStatsKey(ident, uid, set, tag);
- final NetworkStatsHistory existing = mUidStats.get(key);
-
- // update when no existing, or when bucket duration changed
- final long bucketDuration = mSettings.getUidBucketDuration();
- NetworkStatsHistory updated = null;
- if (existing == null) {
- updated = new NetworkStatsHistory(bucketDuration, 10);
- } else if (existing.getBucketDuration() != bucketDuration) {
- updated = new NetworkStatsHistory(
- bucketDuration, estimateResizeBuckets(existing, bucketDuration));
- updated.recordEntireHistory(existing);
- }
-
- if (updated != null) {
- mUidStats.put(key, updated);
- return updated;
- } else {
- return existing;
- }
- }
-
- private void readNetworkDevStatsLocked() {
- if (LOGV) Slog.v(TAG, "readNetworkDevStatsLocked()");
- readNetworkStats(mNetworkDevFile, mNetworkDevStats);
- }
-
- private void readNetworkXtStatsLocked() {
- if (LOGV) Slog.v(TAG, "readNetworkXtStatsLocked()");
- readNetworkStats(mNetworkXtFile, mNetworkXtStats);
- }
-
- private static void readNetworkStats(
- AtomicFile inputFile, HashMap<NetworkIdentitySet, NetworkStatsHistory> output) {
- // clear any existing stats and read from disk
- output.clear();
-
- DataInputStream in = null;
- try {
- in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
-
- // verify file magic header intact
- final int magic = in.readInt();
- if (magic != FILE_MAGIC) {
- throw new ProtocolException("unexpected magic: " + magic);
- }
-
- final int version = in.readInt();
- switch (version) {
- case VERSION_NETWORK_INIT: {
- // network := size *(NetworkIdentitySet NetworkStatsHistory)
- final int size = in.readInt();
- for (int i = 0; i < size; i++) {
- final NetworkIdentitySet ident = new NetworkIdentitySet(in);
- final NetworkStatsHistory history = new NetworkStatsHistory(in);
- output.put(ident, history);
- }
- break;
- }
- default: {
- throw new ProtocolException("unexpected version: " + version);
- }
- }
- } catch (FileNotFoundException e) {
- // missing stats is okay, probably first boot
- } catch (IOException e) {
- Log.wtf(TAG, "problem reading network stats", e);
- } finally {
- IoUtils.closeQuietly(in);
- }
- }
-
- private void ensureUidStatsLoadedLocked() {
- if (!mUidStatsLoaded) {
- readUidStatsLocked();
- mUidStatsLoaded = true;
- }
- }
-
- private void readUidStatsLocked() {
- if (LOGV) Slog.v(TAG, "readUidStatsLocked()");
-
- // clear any existing stats and read from disk
- mUidStats.clear();
-
- DataInputStream in = null;
- try {
- in = new DataInputStream(new BufferedInputStream(mUidFile.openRead()));
-
- // verify file magic header intact
- final int magic = in.readInt();
- if (magic != FILE_MAGIC) {
- throw new ProtocolException("unexpected magic: " + magic);
- }
-
- final int version = in.readInt();
- switch (version) {
- case VERSION_UID_INIT: {
- // uid := size *(UID NetworkStatsHistory)
-
- // drop this data version, since we don't have a good
- // mapping into NetworkIdentitySet.
- break;
- }
- case VERSION_UID_WITH_IDENT: {
- // uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory))
-
- // drop this data version, since this version only existed
- // for a short time.
- break;
- }
- case VERSION_UID_WITH_TAG:
- case VERSION_UID_WITH_SET: {
- // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
- final int identSize = in.readInt();
- for (int i = 0; i < identSize; i++) {
- final NetworkIdentitySet ident = new NetworkIdentitySet(in);
-
- final int size = in.readInt();
- for (int j = 0; j < size; j++) {
- final int uid = in.readInt();
- final int set = (version >= VERSION_UID_WITH_SET) ? in.readInt()
- : SET_DEFAULT;
- final int tag = in.readInt();
-
- final UidStatsKey key = new UidStatsKey(ident, uid, set, tag);
- final NetworkStatsHistory history = new NetworkStatsHistory(in);
- mUidStats.put(key, history);
- }
- }
- break;
- }
- default: {
- throw new ProtocolException("unexpected version: " + version);
- }
- }
- } catch (FileNotFoundException e) {
- // missing stats is okay, probably first boot
- } catch (IOException e) {
- Log.wtf(TAG, "problem reading uid stats", e);
- } finally {
- IoUtils.closeQuietly(in);
- }
- }
-
- private void writeNetworkDevStatsLocked() {
- if (LOGV) Slog.v(TAG, "writeNetworkDevStatsLocked()");
- writeNetworkStats(mNetworkDevStats, mNetworkDevFile);
- }
-
- private void writeNetworkXtStatsLocked() {
- if (LOGV) Slog.v(TAG, "writeNetworkXtStatsLocked()");
- writeNetworkStats(mNetworkXtStats, mNetworkXtFile);
- }
-
- private void writeNetworkStats(
- HashMap<NetworkIdentitySet, NetworkStatsHistory> input, AtomicFile outputFile) {
- // TODO: consider duplicating stats and releasing lock while writing
-
- // trim any history beyond max
- if (mTime.hasCache()) {
- final long systemCurrentTime = System.currentTimeMillis();
- final long trustedCurrentTime = mTime.currentTimeMillis();
-
- final long currentTime = Math.min(systemCurrentTime, trustedCurrentTime);
- final long maxHistory = mSettings.getNetworkMaxHistory();
-
- for (NetworkStatsHistory history : input.values()) {
- final int beforeSize = history.size();
- history.removeBucketsBefore(currentTime - maxHistory);
- final int afterSize = history.size();
-
- if (beforeSize > 24 && afterSize < beforeSize / 2) {
- // yikes, dropping more than half of significant history
- final StringBuilder builder = new StringBuilder();
- builder.append("yikes, dropping more than half of history").append('\n');
- builder.append("systemCurrentTime=").append(systemCurrentTime).append('\n');
- builder.append("trustedCurrentTime=").append(trustedCurrentTime).append('\n');
- builder.append("maxHistory=").append(maxHistory).append('\n');
- builder.append("beforeSize=").append(beforeSize).append('\n');
- builder.append("afterSize=").append(afterSize).append('\n');
- mDropBox.addText(TAG_NETSTATS_ERROR, builder.toString());
- }
- }
- }
-
- FileOutputStream fos = null;
- try {
- fos = outputFile.startWrite();
- final DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fos));
-
- out.writeInt(FILE_MAGIC);
- out.writeInt(VERSION_NETWORK_INIT);
-
- out.writeInt(input.size());
- for (NetworkIdentitySet ident : input.keySet()) {
- final NetworkStatsHistory history = input.get(ident);
- ident.writeToStream(out);
- history.writeToStream(out);
- }
-
- out.flush();
- outputFile.finishWrite(fos);
- } catch (IOException e) {
- Log.wtf(TAG, "problem writing stats", e);
- if (fos != null) {
- outputFile.failWrite(fos);
- }
- }
- }
-
- private void writeUidStatsLocked() {
- if (LOGV) Slog.v(TAG, "writeUidStatsLocked()");
-
- if (!mUidStatsLoaded) {
- Slog.w(TAG, "asked to write UID stats when not loaded; skipping");
- return;
- }
-
- // TODO: consider duplicating stats and releasing lock while writing
-
- // trim any history beyond max
- if (mTime.hasCache()) {
- final long currentTime = Math.min(
- System.currentTimeMillis(), mTime.currentTimeMillis());
- final long maxUidHistory = mSettings.getUidMaxHistory();
- final long maxTagHistory = mSettings.getTagMaxHistory();
- for (UidStatsKey key : mUidStats.keySet()) {
- final NetworkStatsHistory history = mUidStats.get(key);
-
- // detailed tags are trimmed sooner than summary in TAG_NONE
- if (key.tag == TAG_NONE) {
- history.removeBucketsBefore(currentTime - maxUidHistory);
- } else {
- history.removeBucketsBefore(currentTime - maxTagHistory);
- }
- }
- }
-
- // build UidStatsKey lists grouped by ident
- final HashMap<NetworkIdentitySet, ArrayList<UidStatsKey>> keysByIdent = Maps.newHashMap();
- for (UidStatsKey key : mUidStats.keySet()) {
- ArrayList<UidStatsKey> keys = keysByIdent.get(key.ident);
- if (keys == null) {
- keys = Lists.newArrayList();
- keysByIdent.put(key.ident, keys);
- }
- keys.add(key);
- }
-
- FileOutputStream fos = null;
- try {
- fos = mUidFile.startWrite();
- final DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fos));
-
- out.writeInt(FILE_MAGIC);
- out.writeInt(VERSION_UID_WITH_SET);
-
- out.writeInt(keysByIdent.size());
- for (NetworkIdentitySet ident : keysByIdent.keySet()) {
- final ArrayList<UidStatsKey> keys = keysByIdent.get(ident);
- ident.writeToStream(out);
-
- out.writeInt(keys.size());
- for (UidStatsKey key : keys) {
- final NetworkStatsHistory history = mUidStats.get(key);
- out.writeInt(key.uid);
- out.writeInt(key.set);
- out.writeInt(key.tag);
- history.writeToStream(out);
- }
- }
-
- out.flush();
- mUidFile.finishWrite(fos);
- } catch (IOException e) {
- Log.wtf(TAG, "problem writing stats", e);
- if (fos != null) {
- mUidFile.failWrite(fos);
- }
- }
}
@Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
mContext.enforceCallingOrSelfPermission(DUMP, TAG);
final HashSet<String> argSet = new HashSet<String>();
@@ -1411,187 +854,68 @@
argSet.add(arg);
}
- final boolean fullHistory = argSet.contains("full");
+ // usage: dumpsys netstats --full --uid --tag
+ final boolean poll = argSet.contains("--poll") || argSet.contains("poll");
+ final boolean fullHistory = argSet.contains("--full") || argSet.contains("full");
+ final boolean includeUid = argSet.contains("--uid") || argSet.contains("detail");
+ final boolean includeTag = argSet.contains("--tag") || argSet.contains("detail");
+
+ final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
synchronized (mStatsLock) {
- // TODO: remove this testing code, since it corrupts stats
- if (argSet.contains("generate")) {
- generateRandomLocked(args);
- pw.println("Generated stub stats");
- return;
- }
-
- if (argSet.contains("poll")) {
+ if (poll) {
performPollLocked(FLAG_PERSIST_ALL | FLAG_PERSIST_FORCE);
pw.println("Forced poll");
return;
}
pw.println("Active interfaces:");
+ pw.increaseIndent();
for (String iface : mActiveIfaces.keySet()) {
final NetworkIdentitySet ident = mActiveIfaces.get(iface);
- pw.print(" iface="); pw.print(iface);
+ pw.print("iface="); pw.print(iface);
pw.print(" ident="); pw.println(ident.toString());
}
+ pw.decreaseIndent();
- pw.println("Known historical dev stats:");
- for (NetworkIdentitySet ident : mNetworkDevStats.keySet()) {
- final NetworkStatsHistory history = mNetworkDevStats.get(ident);
- pw.print(" ident="); pw.println(ident.toString());
- history.dump(" ", pw, fullHistory);
+ pw.println("Dev stats:");
+ pw.increaseIndent();
+ mDevRecorder.dumpLocked(pw, fullHistory);
+ pw.decreaseIndent();
+
+ if (includeUid) {
+ pw.println("UID stats:");
+ pw.increaseIndent();
+ mUidRecorder.dumpLocked(pw, fullHistory);
+ pw.decreaseIndent();
}
- pw.println("Known historical xt stats:");
- for (NetworkIdentitySet ident : mNetworkXtStats.keySet()) {
- final NetworkStatsHistory history = mNetworkXtStats.get(ident);
- pw.print(" ident="); pw.println(ident.toString());
- history.dump(" ", pw, fullHistory);
- }
-
- if (argSet.contains("detail")) {
- // since explicitly requested with argument, we're okay to load
- // from disk if not already in memory.
- ensureUidStatsLoadedLocked();
-
- final ArrayList<UidStatsKey> keys = Lists.newArrayList();
- keys.addAll(mUidStats.keySet());
- Collections.sort(keys);
-
- pw.println("Detailed UID stats:");
- for (UidStatsKey key : keys) {
- pw.print(" ident="); pw.print(key.ident.toString());
- pw.print(" uid="); pw.print(key.uid);
- pw.print(" set="); pw.print(NetworkStats.setToString(key.set));
- pw.print(" tag="); pw.println(NetworkStats.tagToString(key.tag));
-
- final NetworkStatsHistory history = mUidStats.get(key);
- history.dump(" ", pw, fullHistory);
- }
+ if (includeTag) {
+ pw.println("UID tag stats:");
+ pw.increaseIndent();
+ mUidTagRecorder.dumpLocked(pw, fullHistory);
+ pw.decreaseIndent();
}
}
}
+ private NetworkStats getNetworkStatsSummary() throws RemoteException {
+ return mNetworkManager.getNetworkStatsSummary();
+ }
+
/**
- * @deprecated only for temporary testing
+ * Return snapshot of current UID statistics, including any
+ * {@link TrafficStats#UID_TETHERING} and {@link #mUidOperations} values.
*/
- @Deprecated
- private void generateRandomLocked(String[] args) {
- final long totalBytes = Long.parseLong(args[1]);
- final long totalTime = Long.parseLong(args[2]);
-
- final PackageManager pm = mContext.getPackageManager();
- final ArrayList<Integer> specialUidList = Lists.newArrayList();
- for (int i = 3; i < args.length; i++) {
- try {
- specialUidList.add(pm.getApplicationInfo(args[i], 0).uid);
- } catch (NameNotFoundException e) {
- throw new RuntimeException(e);
- }
- }
+ private NetworkStats getNetworkStatsUidDetail() throws RemoteException {
+ final NetworkStats uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL);
- final HashSet<Integer> otherUidSet = Sets.newHashSet();
- for (ApplicationInfo info : pm.getInstalledApplications(0)) {
- if (pm.checkPermission(android.Manifest.permission.INTERNET, info.packageName)
- == PackageManager.PERMISSION_GRANTED && !specialUidList.contains(info.uid)) {
- otherUidSet.add(info.uid);
- }
- }
+ // fold tethering stats and operations into uid snapshot
+ final NetworkStats tetherSnapshot = getNetworkStatsTethering();
+ uidSnapshot.combineAllValues(tetherSnapshot);
+ uidSnapshot.combineAllValues(mUidOperations);
- final ArrayList<Integer> otherUidList = new ArrayList<Integer>(otherUidSet);
-
- final long end = System.currentTimeMillis();
- final long start = end - totalTime;
-
- mNetworkDevStats.clear();
- mNetworkXtStats.clear();
- mUidStats.clear();
-
- final Random r = new Random();
- for (NetworkIdentitySet ident : mActiveIfaces.values()) {
- final NetworkStatsHistory devHistory = findOrCreateNetworkDevStatsLocked(ident);
- final NetworkStatsHistory xtHistory = findOrCreateNetworkXtStatsLocked(ident);
-
- final ArrayList<Integer> uidList = new ArrayList<Integer>();
- uidList.addAll(specialUidList);
-
- if (uidList.size() == 0) {
- Collections.shuffle(otherUidList);
- uidList.addAll(otherUidList);
- }
-
- boolean first = true;
- long remainingBytes = totalBytes;
- for (int uid : uidList) {
- final NetworkStatsHistory defaultHistory = findOrCreateUidStatsLocked(
- ident, uid, SET_DEFAULT, TAG_NONE);
- final NetworkStatsHistory foregroundHistory = findOrCreateUidStatsLocked(
- ident, uid, SET_FOREGROUND, TAG_NONE);
-
- final long uidBytes = totalBytes / uidList.size();
-
- final float fractionDefault = r.nextFloat();
- final long defaultBytes = (long) (uidBytes * fractionDefault);
- final long foregroundBytes = (long) (uidBytes * (1 - fractionDefault));
-
- defaultHistory.generateRandom(start, end, defaultBytes);
- foregroundHistory.generateRandom(start, end, foregroundBytes);
-
- if (first) {
- final long bumpTime = (start + end) / 2;
- defaultHistory.recordData(
- bumpTime, bumpTime + DAY_IN_MILLIS, 200 * MB_IN_BYTES, 0);
- first = false;
- }
-
- devHistory.recordEntireHistory(defaultHistory);
- devHistory.recordEntireHistory(foregroundHistory);
- xtHistory.recordEntireHistory(defaultHistory);
- xtHistory.recordEntireHistory(foregroundHistory);
- }
- }
- }
-
- private StatsObserver mStatsObserver = new StatsObserver();
-
- private class StatsObserver implements NonMonotonicObserver {
- private String mCurrentType;
-
- public void setCurrentType(String type) {
- mCurrentType = type;
- }
-
- /** {@inheritDoc} */
- public void foundNonMonotonic(
- NetworkStats left, int leftIndex, NetworkStats right, int rightIndex) {
- Log.w(TAG, "found non-monotonic values; saving to dropbox");
-
- // record error for debugging
- final StringBuilder builder = new StringBuilder();
- builder.append("found non-monotonic " + mCurrentType + " values at left[" + leftIndex
- + "] - right[" + rightIndex + "]\n");
- builder.append("left=").append(left).append('\n');
- builder.append("right=").append(right).append('\n');
- mDropBox.addText(TAG_NETSTATS_ERROR, builder.toString());
- }
- }
-
- /**
- * Return the delta between two {@link NetworkStats} snapshots, where {@code
- * before} can be {@code null}.
- */
- private NetworkStats computeStatsDelta(
- NetworkStats before, NetworkStats current, boolean collectStale, String type) {
- if (before != null) {
- mStatsObserver.setCurrentType(type);
- return NetworkStats.subtract(current, before, mStatsObserver);
- } else if (collectStale) {
- // caller is okay collecting stale stats for first call.
- return current;
- } else {
- // this is first snapshot; to prevent from double-counting we only
- // observe traffic occuring between known snapshots.
- return new NetworkStats(0L, 10);
- }
+ return uidSnapshot;
}
/**
@@ -1608,35 +932,6 @@
}
}
- private static NetworkStats computeNetworkXtSnapshotFromUid(NetworkStats uidSnapshot) {
- return uidSnapshot.groupedByIface();
- }
-
- private int estimateNetworkBuckets() {
- return (int) (mSettings.getNetworkMaxHistory() / mSettings.getNetworkBucketDuration());
- }
-
- private int estimateUidBuckets() {
- return (int) (mSettings.getUidMaxHistory() / mSettings.getUidBucketDuration());
- }
-
- private static int estimateResizeBuckets(NetworkStatsHistory existing, long newBucketDuration) {
- return (int) (existing.size() * existing.getBucketDuration() / newBucketDuration);
- }
-
- /**
- * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity}
- * in the given {@link NetworkIdentitySet}.
- */
- private static boolean templateMatches(NetworkTemplate template, NetworkIdentitySet identSet) {
- for (NetworkIdentity ident : identSet) {
- if (template.matches(ident)) {
- return true;
- }
- }
- return false;
- }
-
private Handler.Callback mHandlerCallback = new Handler.Callback() {
/** {@inheritDoc} */
public boolean handleMessage(Message msg) {
@@ -1672,40 +967,22 @@
}
}
- /**
- * Key uniquely identifying a {@link NetworkStatsHistory} for a UID.
- */
- private static class UidStatsKey implements Comparable<UidStatsKey> {
- public final NetworkIdentitySet ident;
- public final int uid;
- public final int set;
- public final int tag;
-
- public UidStatsKey(NetworkIdentitySet ident, int uid, int set, int tag) {
- this.ident = ident;
- this.uid = uid;
- this.set = set;
- this.tag = tag;
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(ident, uid, set, tag);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof UidStatsKey) {
- final UidStatsKey key = (UidStatsKey) obj;
- return Objects.equal(ident, key.ident) && uid == key.uid && set == key.set
- && tag == key.tag;
- }
- return false;
- }
-
+ private class DropBoxNonMonotonicObserver implements NonMonotonicObserver<String> {
/** {@inheritDoc} */
- public int compareTo(UidStatsKey another) {
- return Integer.compare(uid, another.uid);
+ public void foundNonMonotonic(NetworkStats left, int leftIndex, NetworkStats right,
+ int rightIndex, String cookie) {
+ Log.w(TAG, "found non-monotonic values; saving to dropbox");
+
+ // record error for debugging
+ final StringBuilder builder = new StringBuilder();
+ builder.append("found non-monotonic " + cookie + " values at left[" + leftIndex
+ + "] - right[" + rightIndex + "]\n");
+ builder.append("left=").append(left).append('\n');
+ builder.append("right=").append(right).append('\n');
+
+ final DropBoxManager dropBox = (DropBoxManager) mContext.getSystemService(
+ Context.DROPBOX_SERVICE);
+ dropBox.addText(TAG_NETSTATS_ERROR, builder.toString());
}
}
@@ -1731,26 +1008,35 @@
public long getPollInterval() {
return getSecureLong(NETSTATS_POLL_INTERVAL, 30 * MINUTE_IN_MILLIS);
}
- public long getPersistThreshold() {
- return getSecureLong(NETSTATS_PERSIST_THRESHOLD, 2 * MB_IN_BYTES);
- }
- public long getNetworkBucketDuration() {
- return getSecureLong(NETSTATS_NETWORK_BUCKET_DURATION, HOUR_IN_MILLIS);
- }
- public long getNetworkMaxHistory() {
- return getSecureLong(NETSTATS_NETWORK_MAX_HISTORY, 90 * DAY_IN_MILLIS);
- }
- public long getUidBucketDuration() {
- return getSecureLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS);
- }
- public long getUidMaxHistory() {
- return getSecureLong(NETSTATS_UID_MAX_HISTORY, 90 * DAY_IN_MILLIS);
- }
- public long getTagMaxHistory() {
- return getSecureLong(NETSTATS_TAG_MAX_HISTORY, 30 * DAY_IN_MILLIS);
- }
public long getTimeCacheMaxAge() {
- return DAY_IN_MILLIS;
+ return getSecureLong(NETSTATS_TIME_CACHE_MAX_AGE, DAY_IN_MILLIS);
+ }
+ public long getGlobalAlertBytes() {
+ return getSecureLong(NETSTATS_GLOBAL_ALERT_BYTES, 2 * MB_IN_BYTES);
+ }
+ public boolean getSampleEnabled() {
+ return getSecureBoolean(NETSTATS_SAMPLE_ENABLED, true);
+ }
+
+ public Config getDevConfig() {
+ return new Config(getSecureLong(NETSTATS_DEV_BUCKET_DURATION, HOUR_IN_MILLIS),
+ getSecureLong(NETSTATS_DEV_PERSIST_BYTES, 2 * MB_IN_BYTES),
+ getSecureLong(NETSTATS_DEV_ROTATE_AGE, 15 * DAY_IN_MILLIS),
+ getSecureLong(NETSTATS_DEV_DELETE_AGE, 90 * DAY_IN_MILLIS));
+ }
+
+ public Config getUidConfig() {
+ return new Config(getSecureLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS),
+ getSecureLong(NETSTATS_UID_PERSIST_BYTES, 2 * MB_IN_BYTES),
+ getSecureLong(NETSTATS_UID_ROTATE_AGE, 15 * DAY_IN_MILLIS),
+ getSecureLong(NETSTATS_UID_DELETE_AGE, 90 * DAY_IN_MILLIS));
+ }
+
+ public Config getUidTagConfig() {
+ return new Config(getSecureLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS),
+ getSecureLong(NETSTATS_UID_PERSIST_BYTES, 2 * MB_IN_BYTES),
+ getSecureLong(NETSTATS_UID_ROTATE_AGE, 5 * DAY_IN_MILLIS),
+ getSecureLong(NETSTATS_UID_DELETE_AGE, 15 * DAY_IN_MILLIS));
}
}
}
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 24b6064..19d94a1 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -2543,8 +2543,12 @@
if (win == null) {
return 0;
}
- win.mRequestedWidth = requestedWidth;
- win.mRequestedHeight = requestedHeight;
+ if (win.mRequestedWidth != requestedWidth
+ || win.mRequestedHeight != requestedHeight) {
+ win.mLayoutNeeded = true;
+ win.mRequestedWidth = requestedWidth;
+ win.mRequestedHeight = requestedHeight;
+ }
if (attrs != null && seq == win.mSeq) {
win.mSystemUiVisibility = systemUiVisibility;
}
@@ -2565,6 +2569,9 @@
}
flagChanges = win.mAttrs.flags ^= attrs.flags;
attrChanges = win.mAttrs.copyFrom(attrs);
+ if ((attrChanges&WindowManager.LayoutParams.LAYOUT_CHANGED) != 0) {
+ win.mLayoutNeeded = true;
+ }
}
if (DEBUG_LAYOUT) Slog.v(TAG, "Relayout " + win + ": " + win.mAttrs);
@@ -7460,12 +7467,13 @@
// if they want. (We do the normal layout for INVISIBLE
// windows, since that means "perform layout as normal,
// just don't display").
- if (!gone || !win.mHaveFrame) {
+ if (!gone || !win.mHaveFrame || win.mLayoutNeeded) {
if (!win.mLayoutAttached) {
if (initial) {
//Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
win.mContentChanged = false;
}
+ win.mLayoutNeeded = false;
win.prelayout();
mPolicy.layoutWindowLw(win, win.mAttrs, null);
win.mLayoutSeq = seq;
@@ -7497,11 +7505,12 @@
// windows, since that means "perform layout as normal,
// just don't display").
if ((win.mViewVisibility != View.GONE && win.mRelayoutCalled)
- || !win.mHaveFrame) {
+ || !win.mHaveFrame || win.mLayoutNeeded) {
if (initial) {
//Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
win.mContentChanged = false;
}
+ win.mLayoutNeeded = false;
win.prelayout();
mPolicy.layoutWindowLw(win, win.mAttrs, win.mAttachedWindow);
win.mLayoutSeq = seq;
diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java
index 9118381..1067cad 100644
--- a/services/java/com/android/server/wm/WindowState.java
+++ b/services/java/com/android/server/wm/WindowState.java
@@ -238,6 +238,12 @@
// we can give the window focus before waiting for the relayout.
boolean mRelayoutCalled;
+ // If the application has called relayout() with changes that can
+ // impact its window's size, we need to perform a layout pass on it
+ // even if it is not currently visible for layout. This is set
+ // when in that case until the layout is done.
+ boolean mLayoutNeeded;
+
// This is set after the Surface has been created but before the
// window has been drawn. During this time the surface is hidden.
boolean mDrawPending;
@@ -1449,7 +1455,7 @@
return mViewVisibility == View.GONE
|| !mRelayoutCalled
|| (atoken == null && mRootToken.hidden)
- || (atoken != null && atoken.hiddenRequested)
+ || (atoken != null && (atoken.hiddenRequested || atoken.hidden))
|| mAttachedHidden
|| mExiting || mDestroying;
}
@@ -1728,8 +1734,9 @@
pw.print(mPolicyVisibilityAfterAnim);
pw.print(" mAttachedHidden="); pw.println(mAttachedHidden);
}
- if (!mRelayoutCalled) {
- pw.print(prefix); pw.print("mRelayoutCalled="); pw.println(mRelayoutCalled);
+ if (!mRelayoutCalled || mLayoutNeeded) {
+ pw.print(prefix); pw.print("mRelayoutCalled="); pw.print(mRelayoutCalled);
+ pw.print(" mLayoutNeeded="); pw.println(mLayoutNeeded);
}
if (mSurfaceResized || mSurfaceDestroyDeferred) {
pw.print(prefix); pw.print("mSurfaceResized="); pw.print(mSurfaceResized);
diff --git a/services/tests/servicestests/res/raw/netstats_uid_v4 b/services/tests/servicestests/res/raw/netstats_uid_v4
new file mode 100644
index 0000000..e75fc1c
--- /dev/null
+++ b/services/tests/servicestests/res/raw/netstats_uid_v4
Binary files differ
diff --git a/services/tests/servicestests/res/raw/netstats_v1 b/services/tests/servicestests/res/raw/netstats_v1
new file mode 100644
index 0000000..e80860a
--- /dev/null
+++ b/services/tests/servicestests/res/raw/netstats_v1
Binary files differ
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index 90b5a2e..8f5e77e 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -39,6 +39,7 @@
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
import static org.easymock.EasyMock.anyLong;
+import static org.easymock.EasyMock.aryEq;
import static org.easymock.EasyMock.capture;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.eq;
@@ -63,10 +64,12 @@
import android.telephony.TelephonyManager;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.Suppress;
import android.util.TrustedTime;
import com.android.server.net.NetworkStatsService;
import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
+import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
import org.easymock.Capture;
import org.easymock.EasyMock;
@@ -89,6 +92,10 @@
private static final String IMSI_1 = "310004";
private static final String IMSI_2 = "310260";
+ private static final long KB_IN_BYTES = 1024;
+ private static final long MB_IN_BYTES = 1024 * KB_IN_BYTES;
+ private static final long GB_IN_BYTES = 1024 * MB_IN_BYTES;
+
private static NetworkTemplate sTemplateWifi = buildTemplateWifi();
private static NetworkTemplate sTemplateImsi1 = buildTemplateMobileAll(IMSI_1);
private static NetworkTemplate sTemplateImsi2 = buildTemplateMobileAll(IMSI_2);
@@ -282,13 +289,6 @@
mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SHUTDOWN));
verifyAndReset();
- // talk with zombie service to assert stats have gone; and assert that
- // we persisted them to file.
- expectDefaultSettings();
- replay();
- assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
- verifyAndReset();
-
assertStatsFilesExist(true);
// boot through serviceReady() again
@@ -319,6 +319,8 @@
}
+ // TODO: simulate reboot to test bucket resize
+ @Suppress
public void testStatsBucketResize() throws Exception {
NetworkStatsHistory history = null;
@@ -602,7 +604,6 @@
assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 12L, 1280L, 10L, 10);
verifyAndReset();
-
}
public void testSummaryForAllUid() throws Exception {
@@ -755,11 +756,15 @@
expectDefaultSettings();
expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
.addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L));
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L));
+
+ final NetworkStats uidStats = new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L);
final String[] tetherIfacePairs = new String[] { TEST_IFACE, "wlan0" };
- expectNetworkStatsPoll(tetherIfacePairs, new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L, 0L));
+ final NetworkStats tetherStats = new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L, 0L);
+
+ expectNetworkStatsUidDetail(uidStats, tetherIfacePairs, tetherStats);
+ expectNetworkStatsPoll();
replay();
mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
@@ -808,6 +813,9 @@
private void expectNetworkState(NetworkState... state) throws Exception {
expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
+
+ final LinkProperties linkProp = state.length > 0 ? state[0].linkProperties : null;
+ expect(mConnManager.getActiveLinkProperties()).andReturn(linkProp).atLeastOnce();
}
private void expectNetworkStatsSummary(NetworkStats summary) throws Exception {
@@ -815,23 +823,35 @@
}
private void expectNetworkStatsUidDetail(NetworkStats detail) throws Exception {
+ expectNetworkStatsUidDetail(detail, new String[0], new NetworkStats(0L, 0));
+ }
+
+ private void expectNetworkStatsUidDetail(
+ NetworkStats detail, String[] tetherIfacePairs, NetworkStats tetherStats)
+ throws Exception {
expect(mNetManager.getNetworkStatsUidDetail(eq(UID_ALL))).andReturn(detail).atLeastOnce();
+
+ // also include tethering details, since they are folded into UID
+ expect(mConnManager.getTetheredIfacePairs()).andReturn(tetherIfacePairs).atLeastOnce();
+ expect(mNetManager.getNetworkStatsTethering(aryEq(tetherIfacePairs)))
+ .andReturn(tetherStats).atLeastOnce();
}
private void expectDefaultSettings() throws Exception {
expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
}
- private void expectSettings(long persistThreshold, long bucketDuration, long maxHistory)
+ private void expectSettings(long persistBytes, long bucketDuration, long deleteAge)
throws Exception {
expect(mSettings.getPollInterval()).andReturn(HOUR_IN_MILLIS).anyTimes();
- expect(mSettings.getPersistThreshold()).andReturn(persistThreshold).anyTimes();
- expect(mSettings.getNetworkBucketDuration()).andReturn(bucketDuration).anyTimes();
- expect(mSettings.getNetworkMaxHistory()).andReturn(maxHistory).anyTimes();
- expect(mSettings.getUidBucketDuration()).andReturn(bucketDuration).anyTimes();
- expect(mSettings.getUidMaxHistory()).andReturn(maxHistory).anyTimes();
- expect(mSettings.getTagMaxHistory()).andReturn(maxHistory).anyTimes();
expect(mSettings.getTimeCacheMaxAge()).andReturn(DAY_IN_MILLIS).anyTimes();
+ expect(mSettings.getGlobalAlertBytes()).andReturn(MB_IN_BYTES).anyTimes();
+ expect(mSettings.getSampleEnabled()).andReturn(true).anyTimes();
+
+ final Config config = new Config(bucketDuration, persistBytes, deleteAge, deleteAge);
+ expect(mSettings.getDevConfig()).andReturn(config).anyTimes();
+ expect(mSettings.getUidConfig()).andReturn(config).anyTimes();
+ expect(mSettings.getUidTagConfig()).andReturn(config).anyTimes();
}
private void expectCurrentTime() throws Exception {
@@ -843,27 +863,16 @@
}
private void expectNetworkStatsPoll() throws Exception {
- expectNetworkStatsPoll(new String[0], new NetworkStats(getElapsedRealtime(), 0));
- }
-
- private void expectNetworkStatsPoll(String[] tetherIfacePairs, NetworkStats tetherStats)
- throws Exception {
mNetManager.setGlobalAlert(anyLong());
expectLastCall().anyTimes();
- expect(mConnManager.getTetheredIfacePairs()).andReturn(tetherIfacePairs).anyTimes();
- expect(mNetManager.getNetworkStatsTethering(eq(tetherIfacePairs)))
- .andReturn(tetherStats).anyTimes();
}
private void assertStatsFilesExist(boolean exist) {
- final File networkFile = new File(mStatsDir, "netstats.bin");
- final File uidFile = new File(mStatsDir, "netstats_uid.bin");
+ final File basePath = new File(mStatsDir, "netstats");
if (exist) {
- assertTrue(networkFile.exists());
- assertTrue(uidFile.exists());
+ assertTrue(basePath.list().length > 0);
} else {
- assertFalse(networkFile.exists());
- assertFalse(uidFile.exists());
+ assertTrue(basePath.list().length == 0);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java
new file mode 100644
index 0000000..7f05f56
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+package com.android.server.net;
+
+import static android.net.NetworkTemplate.buildTemplateMobileAll;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+
+import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.NetworkIdentity;
+import android.net.NetworkStats;
+import android.net.NetworkTemplate;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import com.android.frameworks.servicestests.R;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+/**
+ * Tests for {@link NetworkStatsCollection}.
+ */
+@MediumTest
+public class NetworkStatsCollectionTest extends AndroidTestCase {
+
+ private static final String TEST_FILE = "test.bin";
+ private static final String TEST_IMSI = "310260000000000";
+
+ public void testReadLegacyNetwork() throws Exception {
+ final File testFile = new File(getContext().getFilesDir(), TEST_FILE);
+ stageFile(R.raw.netstats_v1, testFile);
+
+ final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
+ collection.readLegacyNetwork(testFile);
+
+ // verify that history read correctly
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
+ 636014522L, 709291L, 88037144L, 518820L);
+
+ // now export into a unified format
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ collection.write(new DataOutputStream(bos));
+
+ // clear structure completely
+ collection.reset();
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
+ 0L, 0L, 0L, 0L);
+
+ // and read back into structure, verifying that totals are same
+ collection.read(new ByteArrayInputStream(bos.toByteArray()));
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
+ 636014522L, 709291L, 88037144L, 518820L);
+ }
+
+ public void testReadLegacyUid() throws Exception {
+ final File testFile = new File(getContext().getFilesDir(), TEST_FILE);
+ stageFile(R.raw.netstats_uid_v4, testFile);
+
+ final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
+ collection.readLegacyUid(testFile, false);
+
+ // verify that history read correctly
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
+ 637073904L, 711398L, 88342093L, 521006L);
+
+ // now export into a unified format
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ collection.write(new DataOutputStream(bos));
+
+ // clear structure completely
+ collection.reset();
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
+ 0L, 0L, 0L, 0L);
+
+ // and read back into structure, verifying that totals are same
+ collection.read(new ByteArrayInputStream(bos.toByteArray()));
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
+ 637073904L, 711398L, 88342093L, 521006L);
+ }
+
+ public void testReadLegacyUidTags() throws Exception {
+ final File testFile = new File(getContext().getFilesDir(), TEST_FILE);
+ stageFile(R.raw.netstats_uid_v4, testFile);
+
+ final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
+ collection.readLegacyUid(testFile, true);
+
+ // verify that history read correctly
+ assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI),
+ 77017831L, 100995L, 35436758L, 92344L);
+
+ // now export into a unified format
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ collection.write(new DataOutputStream(bos));
+
+ // clear structure completely
+ collection.reset();
+ assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI),
+ 0L, 0L, 0L, 0L);
+
+ // and read back into structure, verifying that totals are same
+ collection.read(new ByteArrayInputStream(bos.toByteArray()));
+ assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI),
+ 77017831L, 100995L, 35436758L, 92344L);
+ }
+
+ /**
+ * Copy a {@link Resources#openRawResource(int)} into {@link File} for
+ * testing purposes.
+ */
+ private void stageFile(int rawId, File file) throws Exception {
+ new File(file.getParent()).mkdirs();
+ InputStream in = null;
+ OutputStream out = null;
+ try {
+ in = getContext().getResources().openRawResource(rawId);
+ out = new FileOutputStream(file);
+ Streams.copy(in, out);
+ } finally {
+ IoUtils.closeQuietly(in);
+ IoUtils.closeQuietly(out);
+ }
+ }
+
+ public static NetworkIdentitySet buildWifiIdent() {
+ final NetworkIdentitySet set = new NetworkIdentitySet();
+ set.add(new NetworkIdentity(ConnectivityManager.TYPE_WIFI, 0, null, false));
+ return set;
+ }
+
+ private static void assertSummaryTotal(NetworkStatsCollection collection,
+ NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets) {
+ final NetworkStats.Entry entry = collection.getSummary(
+ template, Long.MIN_VALUE, Long.MAX_VALUE).getTotal(null);
+ assertEntry(entry, rxBytes, rxPackets, txBytes, txPackets);
+ }
+
+ private static void assertSummaryTotalIncludingTags(NetworkStatsCollection collection,
+ NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets) {
+ final NetworkStats.Entry entry = collection.getSummary(
+ template, Long.MIN_VALUE, Long.MAX_VALUE).getTotalIncludingTags(null);
+ assertEntry(entry, rxBytes, rxPackets, txBytes, txPackets);
+ }
+
+ private static void assertEntry(
+ NetworkStats.Entry entry, long rxBytes, long rxPackets, long txBytes, long txPackets) {
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ assertEquals("unexpected txPackets", txPackets, entry.txPackets);
+ }
+}
diff --git a/tests/ActivityTests/res/anim/slow_enter.xml b/tests/ActivityTests/res/anim/slow_enter.xml
new file mode 100644
index 0000000..0309643
--- /dev/null
+++ b/tests/ActivityTests/res/anim/slow_enter.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2011, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false" >
+ <scale android:fromXScale="0.9" android:toXScale="1.5"
+ android:fromYScale="0.9" android:toYScale="1.5"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/slow_enter"
+ android:duration="40000" />
+ <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+ android:interpolator="@android:interpolator/decelerate_cubic"
+ android:duration="1000" />
+</set>
diff --git a/tests/ActivityTests/res/anim/slow_exit.xml b/tests/ActivityTests/res/anim/slow_exit.xml
new file mode 100644
index 0000000..6cd3114
--- /dev/null
+++ b/tests/ActivityTests/res/anim/slow_exit.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2011, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false" >
+ <scale android:fromXScale="1.0" android:toXScale="0.9"
+ android:fromYScale="1.0" android:toYScale="0.9"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@android:interpolator/decelerate_quint"
+ android:duration="300" />
+ <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
+ android:interpolator="@android:interpolator/decelerate_cubic"
+ android:duration="300"/>
+</set>
diff --git a/tests/ActivityTests/res/interpolator/slow_enter.xml b/tests/ActivityTests/res/interpolator/slow_enter.xml
new file mode 100644
index 0000000..ddab1aa
--- /dev/null
+++ b/tests/ActivityTests/res/interpolator/slow_enter.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, 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.
+*/
+-->
+
+<cycleInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:cycles="10" />
diff --git a/tests/ActivityTests/res/values/themes.xml b/tests/ActivityTests/res/values/themes.xml
new file mode 100644
index 0000000..67f5938
--- /dev/null
+++ b/tests/ActivityTests/res/values/themes.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<resources>
+ <style name="SlowDialog" parent="@android:style/Theme.Holo.Dialog">
+ <item name="android:windowAnimationStyle">@style/SlowDialog</item>
+ </style>
+ <style name="SlowDialog">
+ <item name="android:windowEnterAnimation">@anim/slow_enter</item>
+ <item name="android:windowExitAnimation">@anim/slow_exit</item>
+ </style>
+</resources>
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
index 583c13c..ae42e29 100644
--- a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
+++ b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
@@ -22,6 +22,7 @@
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityThread;
+import android.app.AlertDialog;
import android.app.Application;
import android.content.ActivityNotFoundException;
import android.os.Bundle;
@@ -35,6 +36,8 @@
import android.widget.TextView;
import android.widget.ScrollView;
import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -101,6 +104,20 @@
}
@Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add("Animate!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override public boolean onMenuItemClick(MenuItem item) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(ActivityTestMain.this,
+ R.style.SlowDialog);
+ builder.setTitle("This is a title");
+ builder.show();
+ return true;
+ }
+ });
+ return true;
+ }
+
+ @Override
protected void onStart() {
super.onStart();
buildUi();
diff --git a/tests/TileBenchmark/res/values/strings.xml b/tests/TileBenchmark/res/values/strings.xml
index 5af52dc..6c7055b 100644
--- a/tests/TileBenchmark/res/values/strings.xml
+++ b/tests/TileBenchmark/res/values/strings.xml
@@ -49,8 +49,9 @@
<!-- Drop down menu entry - automatically scroll to the end of the page
with scrollBy() [CHAR LIMIT=15] -->
<string name="movement_auto_scroll">Auto-scroll</string>
- <!-- Drop down menu entry - [CHAR LIMIT=15] -->
- <string name="movement_auto_fling">Auto-fling</string>
+ <!-- Drop down menu entry - automatically record for a set time before
+ stopping [CHAR LIMIT=15] -->
+ <string name="movement_timed">Timed</string>
<!-- Drop down menu entry - manually navigate the page(s), hit 'capture'
button [CHAR LIMIT=15] -->
<string name="movement_manual">Manual</string>
@@ -67,14 +68,21 @@
<!-- 75th percentile - 75% of frames fall below this value [CHAR LIMIT=12]
-->
<string name="percentile_75">75%ile</string>
+ <!-- standard deviation [CHAR LIMIT=12] -->
+ <string name="std_dev">StdDev</string>
+ <!-- mean [CHAR LIMIT=12] -->
+ <string name="mean">mean</string>
+
+
+
<!-- Frame rate [CHAR LIMIT=15] -->
<string name="frames_per_second">Frames/sec</string>
<!-- Portion of viewport covered by good tiles [CHAR LIMIT=15] -->
<string name="viewport_coverage">Coverage</string>
<!-- Milliseconds taken to inval, and re-render the page [CHAR LIMIT=15] -->
<string name="render_millis">RenderMillis</string>
- <!-- Number of rendering stalls while running the test [CHAR LIMIT=15] -->
- <string name="render_stalls">Stalls</string>
+ <!-- Animation Framerate [CHAR LIMIT=15] -->
+ <string name="animation_framerate">AnimFramerate</string>
<!-- Format string for stat value overlay [CHAR LIMIT=15] -->
<string name="format_stat">%4.4f</string>
diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/PerformanceTest.java b/tests/TileBenchmark/src/com/test/tilebenchmark/PerformanceTest.java
index cc39b75..6356cc1 100644
--- a/tests/TileBenchmark/src/com/test/tilebenchmark/PerformanceTest.java
+++ b/tests/TileBenchmark/src/com/test/tilebenchmark/PerformanceTest.java
@@ -27,17 +27,57 @@
import android.os.Environment;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
+import android.webkit.WebSettings;
+import android.widget.Spinner;
public class PerformanceTest extends
ActivityInstrumentationTestCase2<ProfileActivity> {
+ public static class AnimStat {
+ double aggVal = 0;
+ double aggSqrVal = 0;
+ double count = 0;
+ }
+
private class StatAggregator extends PlaybackGraphs {
private HashMap<String, Double> mDataMap = new HashMap<String, Double>();
+ private HashMap<String, AnimStat> mAnimDataMap = new HashMap<String, AnimStat>();
private int mCount = 0;
+
public void aggregate() {
+ boolean inAnimTests = mAnimTests != null;
+ Resources resources = mWeb.getResources();
+ String animFramerateString = resources.getString(R.string.animation_framerate);
+ for (Map.Entry<String, Double> e : mSingleStats.entrySet()) {
+ String name = e.getKey();
+ if (inAnimTests) {
+ if (name.equals(animFramerateString)) {
+ // in animation testing phase, record animation framerate and aggregate
+ // stats, differentiating on values of mAnimTestNr and mDoubleBuffering
+ String fullName = ANIM_TEST_NAMES[mAnimTestNr] + " " + name;
+ fullName += mDoubleBuffering ? " tiled" : " webkit";
+
+ if (!mAnimDataMap.containsKey(fullName)) {
+ mAnimDataMap.put(fullName, new AnimStat());
+ }
+ AnimStat statVals = mAnimDataMap.get(fullName);
+ statVals.aggVal += e.getValue();
+ statVals.aggSqrVal += e.getValue() * e.getValue();
+ statVals.count += 1;
+ }
+ } else {
+ double aggVal = mDataMap.containsKey(name)
+ ? mDataMap.get(name) : 0;
+ mDataMap.put(name, aggVal + e.getValue());
+ }
+ }
+
+ if (inAnimTests) {
+ return;
+ }
+
mCount++;
- Resources resources = mView.getResources();
for (int metricIndex = 0; metricIndex < Metrics.length; metricIndex++) {
for (int statIndex = 0; statIndex < Stats.length; statIndex++) {
String metricLabel = resources.getString(
@@ -53,34 +93,47 @@
mDataMap.put(label, aggVal);
}
}
- for (Map.Entry<String, Double> e : mSingleStats.entrySet()) {
- double aggVal = mDataMap.containsKey(e.getKey())
- ? mDataMap.get(e.getKey()) : 0;
- mDataMap.put(e.getKey(), aggVal + e.getValue());
- }
+
}
+ // build the final bundle of results
public Bundle getBundle() {
Bundle b = new Bundle();
- int count = 0 == mCount ? Integer.MAX_VALUE : mCount;
+ int count = (0 == mCount) ? Integer.MAX_VALUE : mCount;
for (Map.Entry<String, Double> e : mDataMap.entrySet()) {
b.putDouble(e.getKey(), e.getValue() / count);
}
+
+ for (Map.Entry<String, AnimStat> e : mAnimDataMap.entrySet()) {
+ String statName = e.getKey();
+ AnimStat statVals = e.getValue();
+
+ double avg = statVals.aggVal/statVals.count;
+ double stdDev = Math.sqrt((statVals.aggSqrVal / statVals.count) - avg * avg);
+
+ b.putDouble(statName, avg);
+ b.putDouble(statName + " STD DEV", stdDev);
+ }
+
return b;
}
}
ProfileActivity mActivity;
- ProfiledWebView mView;
- StatAggregator mStats = new StatAggregator();
+ ProfiledWebView mWeb;
+ Spinner mMovementSpinner;
+ StatAggregator mStats;
private static final String LOGTAG = "PerformanceTest";
private static final String TEST_LOCATION = "webkit/page_cycler";
private static final String URL_PREFIX = "file://";
private static final String URL_POSTFIX = "/index.html?skip=true";
private static final int MAX_ITERATIONS = 4;
- private static final String TEST_DIRS[] = {
- "alexa25_2011"//, "alexa_us", "android", "dom", "intl2", "moz", "moz2"
+ private static final String SCROLL_TEST_DIRS[] = {
+ "alexa25_2011"
+ };
+ private static final String ANIM_TEST_DIRS[] = {
+ "dhtml"
};
public PerformanceTest() {
@@ -91,7 +144,22 @@
protected void setUp() throws Exception {
super.setUp();
mActivity = getActivity();
- mView = (ProfiledWebView) mActivity.findViewById(R.id.web);
+ mWeb = (ProfiledWebView) mActivity.findViewById(R.id.web);
+ mMovementSpinner = (Spinner) mActivity.findViewById(R.id.movement);
+ mStats = new StatAggregator();
+
+ // use mStats as a condition variable between the UI thread and
+ // this(the testing) thread
+ mActivity.setCallback(new ProfileCallback() {
+ @Override
+ public void profileCallback(RunData data) {
+ mStats.setData(data);
+ synchronized (mStats) {
+ mStats.notify();
+ }
+ }
+ });
+
}
private boolean loadUrl(final String url) {
@@ -100,12 +168,13 @@
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
- mView.loadUrl(url);
+ mWeb.loadUrl(url);
}
});
synchronized (mStats) {
mStats.wait();
}
+
mStats.aggregate();
} catch (InterruptedException e) {
e.printStackTrace();
@@ -114,15 +183,30 @@
return true;
}
- private boolean runIteration() {
+ private boolean validTest(String nextTest) {
+ // if testing animations, test must be in mAnimTests
+ if (mAnimTests == null)
+ return true;
+
+ for (String test : mAnimTests) {
+ if (test.equals(nextTest)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean runIteration(String[] testDirs) {
File sdFile = Environment.getExternalStorageDirectory();
- for (String testDirName : TEST_DIRS) {
+ for (String testDirName : testDirs) {
File testDir = new File(sdFile, TEST_LOCATION + "/" + testDirName);
Log.d(LOGTAG, "Testing dir: '" + testDir.getAbsolutePath()
+ "', exists=" + testDir.exists());
+
for (File siteDir : testDir.listFiles()) {
- if (!siteDir.isDirectory())
+ if (!siteDir.isDirectory() || !validTest(siteDir.getName())) {
continue;
+ }
if (!loadUrl(URL_PREFIX + siteDir.getAbsolutePath()
+ URL_POSTFIX)) {
@@ -133,7 +217,44 @@
return true;
}
- public void testMetrics() {
+ private boolean runTestDirs(String[] testDirs) {
+ for (int i = 0; i < MAX_ITERATIONS; i++)
+ if (!runIteration(testDirs)) {
+ return false;
+ }
+ return true;
+ }
+
+ private void pushDoubleBuffering() {
+ getInstrumentation().runOnMainSync(new Runnable() {
+ public void run() {
+ mWeb.setDoubleBuffering(mDoubleBuffering);
+ }
+ });
+ }
+
+ private void setScrollingTestingMode(final boolean scrolled) {
+ getInstrumentation().runOnMainSync(new Runnable() {
+ public void run() {
+ mMovementSpinner.setSelection(scrolled ? 0 : 2);
+ }
+ });
+ }
+
+
+ private String[] mAnimTests = null;
+ private int mAnimTestNr = -1;
+ private boolean mDoubleBuffering = true;
+ private static final String[] ANIM_TEST_NAMES = {
+ "slow", "fast"
+ };
+ private static final String[][] ANIM_TESTS = {
+ {"scrolling", "replaceimages", "layers5", "layers1"},
+ {"slidingballs", "meter", "slidein", "fadespacing", "colorfade",
+ "mozilla", "movingtext", "diagball", "zoom", "imageslide"},
+ };
+
+ private boolean checkMedia() {
String state = Environment.getExternalStorageState();
if (!Environment.MEDIA_MOUNTED.equals(state)
@@ -141,27 +262,43 @@
Log.d(LOGTAG, "ARG Can't access sd card!");
// Can't read the SD card, fail and die!
getInstrumentation().sendStatus(1, null);
- return;
+ return false;
}
+ return true;
+ }
- // use mGraphs as a condition variable between the UI thread and
- // this(the testing) thread
- mActivity.setCallback(new ProfileCallback() {
- @Override
- public void profileCallback(RunData data) {
- Log.d(LOGTAG, "test completion callback");
- mStats.setData(data);
- synchronized (mStats) {
- mStats.notify();
+ public void testMetrics() {
+ setScrollingTestingMode(true);
+ if (checkMedia() && runTestDirs(SCROLL_TEST_DIRS)) {
+ getInstrumentation().sendStatus(0, mStats.getBundle());
+ } else {
+ getInstrumentation().sendStatus(1, null);
+ }
+ }
+
+ private boolean runAnimationTests() {
+ for (int doubleBuffer = 0; doubleBuffer <= 1; doubleBuffer++) {
+ mDoubleBuffering = doubleBuffer == 1;
+ pushDoubleBuffering();
+ for (mAnimTestNr = 0; mAnimTestNr < ANIM_TESTS.length; mAnimTestNr++) {
+ mAnimTests = ANIM_TESTS[mAnimTestNr];
+ if (!runTestDirs(ANIM_TEST_DIRS)) {
+ return false;
}
}
- });
+ }
+ return true;
+ }
- for (int i = 0; i < MAX_ITERATIONS; i++)
- if (!runIteration()) {
- getInstrumentation().sendStatus(1, null);
- return;
- }
- getInstrumentation().sendStatus(0, mStats.getBundle());
+ public void testAnimations() {
+ // instead of autoscrolling, load each page until either an timer fires,
+ // or the animation signals complete via javascript
+ setScrollingTestingMode(false);
+
+ if (checkMedia() && runAnimationTests()) {
+ getInstrumentation().sendStatus(0, mStats.getBundle());
+ } else {
+ getInstrumentation().sendStatus(1, null);
+ }
}
}
diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackGraphs.java b/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackGraphs.java
index 9ea90f8..a3ae9be 100644
--- a/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackGraphs.java
+++ b/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackGraphs.java
@@ -80,7 +80,7 @@
for (int tileID = 1; tileID < frame.length; tileID++) {
TileData data = frame[tileID];
double coverage = viewportCoverage(frame[0], data);
- total += coverage * (data.isReady ? 1 : 0);
+ total += coverage * (data.isReady ? 100 : 0);
totalCount += coverage;
}
if (totalCount == 0) {
@@ -91,7 +91,7 @@
@Override
public double getMax() {
- return 1;
+ return 100;
}
@Override
@@ -108,6 +108,9 @@
}
public static double getPercentile(double sortedValues[], double ratioAbove) {
+ if (sortedValues.length == 0)
+ return -1;
+
double index = ratioAbove * (sortedValues.length - 1);
int intIndex = (int) Math.floor(index);
if (index == intIndex) {
@@ -118,6 +121,31 @@
+ sortedValues[intIndex + 1] * (alpha);
}
+ public static double getMean(double sortedValues[]) {
+ if (sortedValues.length == 0)
+ return -1;
+
+ double agg = 0;
+ for (double val : sortedValues) {
+ agg += val;
+ }
+ return agg / sortedValues.length;
+ }
+
+ public static double getStdDev(double sortedValues[]) {
+ if (sortedValues.length == 0)
+ return -1;
+
+ double agg = 0;
+ double sqrAgg = 0;
+ for (double val : sortedValues) {
+ agg += val;
+ sqrAgg += val*val;
+ }
+ double mean = agg / sortedValues.length;
+ return Math.sqrt((sqrAgg / sortedValues.length) - (mean * mean));
+ }
+
protected static StatGen[] Stats = new StatGen[] {
new StatGen() {
@Override
@@ -149,6 +177,26 @@
public int getLabelId() {
return R.string.percentile_75;
}
+ }, new StatGen() {
+ @Override
+ public double getValue(double[] sortedValues) {
+ return getStdDev(sortedValues);
+ }
+
+ @Override
+ public int getLabelId() {
+ return R.string.std_dev;
+ }
+ }, new StatGen() {
+ @Override
+ public double getValue(double[] sortedValues) {
+ return getMean(sortedValues);
+ }
+
+ @Override
+ public int getLabelId() {
+ return R.string.mean;
+ }
},
};
@@ -159,40 +207,47 @@
}
private ArrayList<ShapeDrawable> mShapes = new ArrayList<ShapeDrawable>();
- protected double[][] mStats = new double[Metrics.length][Stats.length];
+ protected final double[][] mStats = new double[Metrics.length][Stats.length];
protected HashMap<String, Double> mSingleStats;
+ private void gatherFrameMetric(int metricIndex, double metricValues[], RunData data) {
+ // create graph out of rectangles, one per frame
+ int lastBar = 0;
+ for (int frameIndex = 0; frameIndex < data.frames.length; frameIndex++) {
+ TileData frame[] = data.frames[frameIndex];
+ int newBar = (frame[0].top + frame[0].bottom) / 2;
+
+ MetricGen s = Metrics[metricIndex];
+ double absoluteValue = s.getValue(frame);
+ double relativeValue = absoluteValue / s.getMax();
+ relativeValue = Math.min(1,relativeValue);
+ relativeValue = Math.max(0,relativeValue);
+ int rightPos = (int) (-BAR_WIDTH * metricIndex);
+ int leftPos = (int) (-BAR_WIDTH * (metricIndex + relativeValue));
+
+ ShapeDrawable graphBar = new ShapeDrawable();
+ graphBar.getPaint().setColor(Color.BLUE);
+ graphBar.setBounds(leftPos, lastBar, rightPos, newBar);
+
+ mShapes.add(graphBar);
+ metricValues[frameIndex] = absoluteValue;
+ lastBar = newBar;
+ }
+ }
+
public void setData(RunData data) {
mShapes.clear();
double metricValues[] = new double[data.frames.length];
+ mSingleStats = data.singleStats;
+
if (data.frames.length == 0) {
return;
}
for (int metricIndex = 0; metricIndex < Metrics.length; metricIndex++) {
- // create graph out of rectangles, one per frame
- int lastBar = 0;
- for (int frameIndex = 0; frameIndex < data.frames.length; frameIndex++) {
- TileData frame[] = data.frames[frameIndex];
- int newBar = (frame[0].top + frame[0].bottom) / 2;
-
- MetricGen s = Metrics[metricIndex];
- double absoluteValue = s.getValue(frame);
- double relativeValue = absoluteValue / s.getMax();
- relativeValue = Math.min(1,relativeValue);
- relativeValue = Math.max(0,relativeValue);
- int rightPos = (int) (-BAR_WIDTH * metricIndex);
- int leftPos = (int) (-BAR_WIDTH * (metricIndex + relativeValue));
-
- ShapeDrawable graphBar = new ShapeDrawable();
- graphBar.getPaint().setColor(Color.BLUE);
- graphBar.setBounds(leftPos, lastBar, rightPos, newBar);
-
- mShapes.add(graphBar);
- metricValues[frameIndex] = absoluteValue;
- lastBar = newBar;
- }
+ // calculate metric based on list of frames
+ gatherFrameMetric(metricIndex, metricValues, data);
// store aggregate statistics per metric (median, and similar)
Arrays.sort(metricValues);
@@ -200,8 +255,6 @@
mStats[metricIndex][statIndex] =
Stats[statIndex].getValue(metricValues);
}
-
- mSingleStats = data.singleStats;
}
}
diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfileActivity.java b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfileActivity.java
index d38d006..2e77157 100644
--- a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfileActivity.java
+++ b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfileActivity.java
@@ -22,11 +22,12 @@
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.util.Log;
import android.util.Pair;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
-import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.AdapterView;
@@ -49,6 +50,8 @@
*/
public class ProfileActivity extends Activity {
+ private static final int TIMED_RECORD_MILLIS = 2000;
+
public interface ProfileCallback {
public void profileCallback(RunData data);
}
@@ -65,6 +68,7 @@
LoggingWebViewClient mLoggingWebViewClient = new LoggingWebViewClient();
AutoLoggingWebViewClient mAutoLoggingWebViewClient = new AutoLoggingWebViewClient();
+ TimedLoggingWebViewClient mTimedLoggingWebViewClient = new TimedLoggingWebViewClient();
private enum TestingState {
NOT_TESTING,
@@ -93,18 +97,18 @@
public void onItemSelected(AdapterView<?> parent, View view,
int position, long id) {
String movementStr = parent.getItemAtPosition(position).toString();
- if (movementStr == getResources().getString(
- R.string.movement_auto_scroll)
- || movementStr == getResources().getString(
- R.string.movement_auto_fling)) {
+ if (movementStr == getResources().getString(R.string.movement_auto_scroll)) {
mWeb.setWebViewClient(mAutoLoggingWebViewClient);
mCaptureButton.setEnabled(false);
mVelocitySpinner.setEnabled(true);
- } else if (movementStr == getResources().getString(
- R.string.movement_manual)) {
+ } else if (movementStr == getResources().getString(R.string.movement_manual)) {
mWeb.setWebViewClient(mLoggingWebViewClient);
mCaptureButton.setEnabled(true);
mVelocitySpinner.setEnabled(false);
+ } else if (movementStr == getResources().getString(R.string.movement_timed)) {
+ mWeb.setWebViewClient(mTimedLoggingWebViewClient);
+ mCaptureButton.setEnabled(false);
+ mVelocitySpinner.setEnabled(false);
}
}
@@ -124,15 +128,19 @@
super.onPageStarted(view, url, favicon);
mUrl.setText(url);
}
- }
-
- private class AutoLoggingWebViewClient extends LoggingWebViewClient {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
view.requestFocus();
+ ((ProfiledWebView)view).onPageFinished();
+ }
+ }
+ private class AutoLoggingWebViewClient extends LoggingWebViewClient {
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ super.onPageFinished(view, url);
startViewProfiling(true);
}
@@ -143,6 +151,32 @@
}
}
+ private class TimedLoggingWebViewClient extends LoggingWebViewClient {
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ super.onPageFinished(view, url);
+ startViewProfiling(false);
+
+ // after a fixed time after page finished, stop testing
+ new CountDownTimer(TIMED_RECORD_MILLIS, TIMED_RECORD_MILLIS) {
+ @Override
+ public void onTick(long millisUntilFinished) {
+ }
+
+ @Override
+ public void onFinish() {
+ mWeb.stopScrollTest();
+ }
+ }.start();
+ }
+
+ @Override
+ public void onPageStarted(WebView view, String url, Bitmap favicon) {
+ super.onPageStarted(view, url, favicon);
+ setTestingState(TestingState.PRE_TESTING);
+ }
+ }
+
private class StoreFileTask extends
AsyncTask<Pair<String, RunData>, Void, Void> {
@@ -178,11 +212,13 @@
mMovementSpinner.setEnabled(false);
break;
case START_TESTING:
+ mCaptureButton.setChecked(true);
mUrl.setBackgroundResource(R.color.background_start_testing);
mInspectButton.setEnabled(false);
mMovementSpinner.setEnabled(false);
break;
case STOP_TESTING:
+ mCaptureButton.setChecked(false);
mUrl.setBackgroundResource(R.color.background_stop_testing);
break;
case SAVED_TESTING:
@@ -195,7 +231,6 @@
/** auto - automatically scroll. */
private void startViewProfiling(boolean auto) {
// toggle capture button to indicate capture state to user
- mCaptureButton.setChecked(true);
mWeb.startScrollTest(mCallback, auto);
setTestingState(TestingState.START_TESTING);
}
@@ -217,7 +252,7 @@
public void profileCallback(RunData data) {
new StoreFileTask().execute(new Pair<String, RunData>(
TEMP_FILENAME, data));
- mCaptureButton.setChecked(false);
+ Log.d("ProfileActivity", "stored " + data.frames.length + " frames in file");
setTestingState(TestingState.STOP_TESTING);
}
});
@@ -245,8 +280,8 @@
// Movement spinner
String content[] = {
getResources().getString(R.string.movement_auto_scroll),
- getResources().getString(R.string.movement_auto_fling),
- getResources().getString(R.string.movement_manual)
+ getResources().getString(R.string.movement_manual),
+ getResources().getString(R.string.movement_timed)
};
adapter = new ArrayAdapter<CharSequence>(this,
android.R.layout.simple_spinner_item, content);
@@ -270,13 +305,7 @@
});
// Custom profiling WebView
- WebSettings settings = mWeb.getSettings();
- settings.setJavaScriptEnabled(true);
- settings.setSupportZoom(true);
- settings.setEnableSmoothTransition(true);
- settings.setBuiltInZoomControls(true);
- settings.setLoadWithOverviewMode(true);
- settings.setProperty("use_minimal_memory", "false"); // prefetch tiles, as browser does
+ mWeb.init(this);
mWeb.setWebViewClient(new LoggingWebViewClient());
// URL text entry
diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java
index 83f1668..a706f78 100644
--- a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java
+++ b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java
@@ -20,21 +20,28 @@
import android.os.CountDownTimer;
import android.util.AttributeSet;
import android.util.Log;
+import android.webkit.WebSettings;
import android.webkit.WebView;
+import android.widget.Toast;
+
+import java.util.ArrayList;
import com.test.tilebenchmark.ProfileActivity.ProfileCallback;
import com.test.tilebenchmark.RunData.TileData;
public class ProfiledWebView extends WebView {
+ private static final String LOGTAG = "ProfiledWebView";
+
private int mSpeed;
private boolean mIsTesting = false;
private boolean mIsScrolling = false;
private ProfileCallback mCallback;
private long mContentInvalMillis;
- private boolean mHadToBeForced = false;
private static final int LOAD_STALL_MILLIS = 2000; // nr of millis after load,
// before test is forced
+ private double mLoadTime;
+ private double mAnimationTime;
public ProfiledWebView(Context context) {
super(context);
@@ -53,6 +60,39 @@
super(context, attrs, defStyle, privateBrowsing);
}
+ private class JavaScriptInterface {
+ Context mContext;
+
+ /** Instantiate the interface and set the context */
+ JavaScriptInterface(Context c) {
+ mContext = c;
+ }
+
+ /** Show a toast from the web page */
+ public void animationComplete() {
+ Toast.makeText(mContext, "Animation complete!", Toast.LENGTH_SHORT).show();
+ //Log.d(LOGTAG, "anim complete");
+ mAnimationTime = System.currentTimeMillis();
+ }
+ }
+
+ public void init(Context c) {
+ WebSettings settings = getSettings();
+ settings.setJavaScriptEnabled(true);
+ settings.setSupportZoom(true);
+ settings.setEnableSmoothTransition(true);
+ settings.setBuiltInZoomControls(true);
+ settings.setLoadWithOverviewMode(true);
+ settings.setProperty("use_minimal_memory", "false"); // prefetch tiles, as browser does
+ addJavascriptInterface(new JavaScriptInterface(c), "Android");
+ mAnimationTime = 0;
+ mLoadTime = 0;
+ }
+
+ public void onPageFinished() {
+ mLoadTime = System.currentTimeMillis();
+ }
+
@Override
protected void onDraw(android.graphics.Canvas canvas) {
if (mIsTesting && mIsScrolling) {
@@ -72,9 +112,12 @@
* scrolling, invalidate all content and redraw it, measuring time taken.
*/
public void startScrollTest(ProfileCallback callback, boolean autoScrolling) {
- mIsScrolling = autoScrolling;
mCallback = callback;
mIsTesting = false;
+ mIsScrolling = false;
+ WebSettings settings = getSettings();
+ settings.setProperty("tree_updates", "0");
+
if (autoScrolling) {
// after a while, force it to start even if the pages haven't swapped
@@ -86,13 +129,18 @@
@Override
public void onFinish() {
// invalidate all content, and kick off redraw
+ Log.d("ProfiledWebView",
+ "kicking off test with callback registration, and tile discard...");
registerPageSwapCallback();
discardAllTextures();
invalidate();
-
+ mIsScrolling = true;
mContentInvalMillis = System.currentTimeMillis();
}
}.start();
+ } else {
+ mIsTesting = true;
+ tileProfilingStart();
}
}
@@ -102,13 +150,35 @@
*/
@Override
protected void pageSwapCallback(boolean startAnim) {
- mContentInvalMillis = System.currentTimeMillis() - mContentInvalMillis;
- super.pageSwapCallback(startAnim);
- Log.d("ProfiledWebView", "REDRAW TOOK " + mContentInvalMillis
- + "millis");
- mIsTesting = true;
- invalidate(); // ensure a redraw so that auto-scrolling can occur
- tileProfilingStart();
+ if (!mIsTesting && mIsScrolling) {
+ // kick off testing
+ mContentInvalMillis = System.currentTimeMillis() - mContentInvalMillis;
+ super.pageSwapCallback(startAnim);
+ Log.d("ProfiledWebView", "REDRAW TOOK " + mContentInvalMillis + "millis");
+ mIsTesting = true;
+ invalidate(); // ensure a redraw so that auto-scrolling can occur
+ tileProfilingStart();
+ }
+ }
+
+ private double animFramerate() {
+ WebSettings settings = getSettings();
+ String updatesString = settings.getProperty("tree_updates");
+ int updates = (updatesString == null) ? -1 : Integer.parseInt(updatesString);
+
+ double animationTime;
+ if (mAnimationTime == 0) {
+ animationTime = System.currentTimeMillis() - mLoadTime;
+ } else {
+ animationTime = mAnimationTime - mLoadTime;
+ }
+
+ return updates * 1000 / animationTime;
+ }
+
+ public void setDoubleBuffering(boolean useDoubleBuffering) {
+ WebSettings settings = getSettings();
+ settings.setProperty("use_double_buffering", useDoubleBuffering ? "true" : "false");
}
/*
@@ -127,11 +197,12 @@
// record the time spent (before scrolling) rendering the page
data.singleStats.put(getResources().getString(R.string.render_millis),
(double)mContentInvalMillis);
- // record if the page render timed out
- Log.d("ProfiledWebView", "hadtobeforced = " + mHadToBeForced);
- data.singleStats.put(getResources().getString(R.string.render_stalls),
- mHadToBeForced ? 1.0 : 0.0);
- mHadToBeForced = false;
+
+ // record framerate
+ double framerate = animFramerate();
+ Log.d(LOGTAG, "anim framerate was "+framerate);
+ data.singleStats.put(getResources().getString(R.string.animation_framerate),
+ framerate);
for (int frame = 0; frame < data.frames.length; frame++) {
data.frames[frame] = new TileData[
@@ -159,6 +230,8 @@
@Override
public void loadUrl(String url) {
+ mAnimationTime = 0;
+ mLoadTime = 0;
if (!url.startsWith("http://") && !url.startsWith("file://")) {
url = "http://" + url;
}