Merge "Add a DPM api to check if a pkg is restricted to use metered data."
diff --git a/apct-tests/perftests/core/src/android/text/MeasuredTextMemoryUsageTest.java b/apct-tests/perftests/core/src/android/text/MeasuredTextMemoryUsageTest.java
new file mode 100644
index 0000000..fc6302e
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/text/MeasuredTextMemoryUsageTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2018 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 android.text;
+
+import static android.text.TextDirectionHeuristics.LTR;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.content.res.ColorStateList;
+import android.graphics.Canvas;
+import android.graphics.Typeface;
+import android.text.Layout;
+import android.text.style.TextAppearanceSpan;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.CharBuffer;
+import java.util.Random;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class MeasuredTextMemoryUsageTest {
+    private static final int WORD_LENGTH = 9;  // Random word has 9 characters.
+    private static final boolean NO_STYLE_TEXT = false;
+
+    private static TextPaint PAINT = new TextPaint();
+
+    private static int TRIAL_COUNT = 100;
+
+    public MeasuredTextMemoryUsageTest() {}
+
+    private TextPerfUtils mTextUtil = new TextPerfUtils();
+
+    @Before
+    public void setUp() {
+        mTextUtil.resetRandom(0 /* seed */);
+    }
+
+    private void reportMemoryUsage(int memoryUsage, String key) {
+        Bundle status = new Bundle();
+        status.putInt(key + "_median", memoryUsage);
+        InstrumentationRegistry.getInstrumentation().sendStatus(Activity.RESULT_OK, status);
+    }
+
+    private int median(int[] values) {
+        return values.length % 2 == 0 ?
+                (values[values.length / 2] + values[values.length / 2 - 1]) / 2:
+                values[values.length / 2];
+    }
+
+    @Test
+    public void testMemoryUsage_NoHyphenation() {
+        int[] memories = new int[TRIAL_COUNT];
+        // Report median of randomly generated MeasuredText.
+        for (int i = 0; i < TRIAL_COUNT; ++i) {
+            memories[i] = new MeasuredText.Builder(
+                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+                .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
+                .build().getMemoryUsage();
+        }
+        reportMemoryUsage(median(memories), "MemoryUsage_NoHyphenation");
+    }
+
+    @Test
+    public void testMemoryUsage_Hyphenation() {
+        int[] memories = new int[TRIAL_COUNT];
+        // Report median of randomly generated MeasuredText.
+        for (int i = 0; i < TRIAL_COUNT; ++i) {
+            memories[i] = new MeasuredText.Builder(
+                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+                .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
+                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+                .build().getMemoryUsage();
+        }
+        reportMemoryUsage(median(memories), "MemoryUsage_Hyphenation");
+    }
+
+    @Test
+    public void testMemoryUsage_NoHyphenation_WidthOnly() {
+        int[] memories = new int[TRIAL_COUNT];
+        // Report median of randomly generated MeasuredText.
+        for (int i = 0; i < TRIAL_COUNT; ++i) {
+            memories[i] = new MeasuredText.Builder(
+                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+                .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
+                .build(false /* width only */).getMemoryUsage();
+        }
+        reportMemoryUsage(median(memories), "MemoryUsage_NoHyphenation_WidthOnly");
+    }
+
+    @Test
+    public void testMemoryUsage_Hyphenatation_WidthOnly() {
+        int[] memories = new int[TRIAL_COUNT];
+        // Report median of randomly generated MeasuredText.
+        for (int i = 0; i < TRIAL_COUNT; ++i) {
+            memories[i] = new MeasuredText.Builder(
+                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+                .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
+                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+                .build(false /* width only */).getMemoryUsage();
+        }
+        reportMemoryUsage(median(memories), "MemoryUsage_Hyphenation_WidthOnly");
+    }
+}
diff --git a/apct-tests/perftests/core/src/android/text/MeasuredTextPerfTest.java b/apct-tests/perftests/core/src/android/text/MeasuredTextPerfTest.java
new file mode 100644
index 0000000..98f2bd5
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/text/MeasuredTextPerfTest.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2018 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 android.text;
+
+import static android.text.TextDirectionHeuristics.LTR;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import android.content.res.ColorStateList;
+import android.graphics.Canvas;
+import android.graphics.Typeface;
+import android.text.Layout;
+import android.text.style.TextAppearanceSpan;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.CharBuffer;
+import java.util.Random;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class MeasuredTextPerfTest {
+    private static final int WORD_LENGTH = 9;  // Random word has 9 characters.
+    private static final int WORDS_IN_LINE = 8;  // Roughly, 8 words in a line.
+    private static final boolean NO_STYLE_TEXT = false;
+    private static final boolean STYLE_TEXT = true;
+
+    private static TextPaint PAINT = new TextPaint();
+    private static final int TEXT_WIDTH = WORDS_IN_LINE * WORD_LENGTH * (int) PAINT.getTextSize();
+
+    public MeasuredTextPerfTest() {}
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    private TextPerfUtils mTextUtil = new TextPerfUtils();
+
+    @Before
+    public void setUp() {
+        mTextUtil.resetRandom(0 /* seed */);
+    }
+
+    @Test
+    public void testCreate_NoStyled_Hyphenation() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
+            state.resumeTiming();
+
+            new MeasuredText.Builder(text, PAINT)
+                    .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
+                    .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+                    .build(true /* do full layout */);
+        }
+    }
+
+    @Test
+    public void testCreate_NoStyled_NoHyphenation() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
+            state.resumeTiming();
+
+            new MeasuredText.Builder(text, PAINT)
+                    .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+                    .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
+                    .build(true /* do full layout */);
+        }
+    }
+
+    @Test
+    public void testCreate_NoStyled_Hyphenation_WidthOnly() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
+            state.resumeTiming();
+
+            new MeasuredText.Builder(text, PAINT)
+                    .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
+                    .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+                    .build(false /* width only */);
+        }
+    }
+
+    @Test
+    public void testCreate_NoStyled_NoHyphenation_WidthOnly() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
+            state.resumeTiming();
+
+            new MeasuredText.Builder(text, PAINT)
+                    .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+                    .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
+                    .build(false /* width only */);
+        }
+    }
+
+    @Test
+    public void testCreate_Styled_Hyphenation() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT);
+            state.resumeTiming();
+
+            new MeasuredText.Builder(text, PAINT)
+                    .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
+                    .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+                    .build(true /* do full layout */);
+        }
+    }
+
+    @Test
+    public void testCreate_Styled_NoHyphenation() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT);
+            state.resumeTiming();
+
+            new MeasuredText.Builder(text, PAINT)
+                    .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+                    .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
+                    .build(true /* do full layout */);
+        }
+    }
+
+    @Test
+    public void testCreate_Styled_Hyphenation_WidthOnly() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT);
+            state.resumeTiming();
+
+            new MeasuredText.Builder(text, PAINT)
+                    .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
+                    .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+                    .build(false /* width only */);
+        }
+    }
+
+    @Test
+    public void testCreate_Styled_NoHyphenation_WidthOnly() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT);
+            state.resumeTiming();
+
+            new MeasuredText.Builder(text, PAINT)
+                    .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+                    .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
+                    .build(false /* width only */);
+        }
+    }
+}
diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
index 682885b..bab2a85 100644
--- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
@@ -43,74 +43,30 @@
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class StaticLayoutPerfTest {
+    private static final int WORD_LENGTH = 9;  // Random word has 9 characters.
+    private static final int WORDS_IN_LINE = 8;  // Roughly, 8 words in a line.
+    private static final boolean NO_STYLE_TEXT = false;
+    private static final boolean STYLE_TEXT = true;
+
+    private static TextPaint PAINT = new TextPaint();
+    private static final int TEXT_WIDTH = WORDS_IN_LINE * WORD_LENGTH * (int) PAINT.getTextSize();
 
     public StaticLayoutPerfTest() {}
 
     @Rule
     public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
-    private static final int WORD_LENGTH = 9;  // Random word has 9 characters.
-    private static final int WORDS_IN_LINE = 8;  // Roughly, 8 words in a line.
-    private static final int PARA_LENGTH = 500;  // Number of characters in a paragraph.
-
-    private static final boolean NO_STYLE_TEXT = false;
-    private static final boolean STYLE_TEXT = true;
-
-    private Random mRandom;
-
-    private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
-    private static final int ALPHABET_LENGTH = ALPHABET.length();
-
-    private static final ColorStateList TEXT_COLOR = ColorStateList.valueOf(0x00000000);
-    private static final String[] FAMILIES = { "sans-serif", "serif", "monospace" };
-    private static final int[] STYLES = {
-            Typeface.NORMAL, Typeface.BOLD, Typeface.ITALIC, Typeface.BOLD_ITALIC
-    };
-
-    private final char[] mBuffer = new char[PARA_LENGTH];
-
-    private static TextPaint PAINT = new TextPaint();
-    private static final int TEXT_WIDTH = WORDS_IN_LINE * WORD_LENGTH * (int) PAINT.getTextSize();
-
-    private CharSequence generateRandomParagraph(int wordLen, boolean applyRandomStyle) {
-        for (int i = 0; i < PARA_LENGTH; i++) {
-            if (i % (wordLen + 1) == wordLen) {
-                mBuffer[i] = ' ';
-            } else {
-                mBuffer[i] = ALPHABET.charAt(mRandom.nextInt(ALPHABET_LENGTH));
-            }
-        }
-
-        CharSequence cs = CharBuffer.wrap(mBuffer);
-        if (!applyRandomStyle) {
-            return cs;
-        }
-
-        SpannableStringBuilder ssb = new SpannableStringBuilder(cs);
-        for (int i = 0; i < ssb.length(); i += WORD_LENGTH) {
-            final int spanStart = i;
-            final int spanEnd = (i + WORD_LENGTH) > ssb.length() ? ssb.length() : i + WORD_LENGTH;
-
-            final TextAppearanceSpan span = new TextAppearanceSpan(
-                  FAMILIES[mRandom.nextInt(FAMILIES.length)],
-                  STYLES[mRandom.nextInt(STYLES.length)],
-                  24 + mRandom.nextInt(32),  // text size. min 24 max 56
-                  TEXT_COLOR, TEXT_COLOR);
-
-            ssb.setSpan(span, spanStart, spanEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
-        }
-        return ssb;
-    }
+    private TextPerfUtils mTextUtil = new TextPerfUtils();
 
     @Before
     public void setUp() {
-        mRandom = new Random(0);
+        mTextUtil.resetRandom(0 /* seed */);
     }
 
     @Test
     public void testCreate_FixedText_NoStyle_Greedy_NoHyphenation() {
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
+        final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
         while (state.keepRunning()) {
             StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
                     .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
@@ -124,7 +80,7 @@
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             state.pauseTiming();
-            final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
             state.resumeTiming();
 
             StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
@@ -139,7 +95,7 @@
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             state.pauseTiming();
-            final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
             state.resumeTiming();
 
             StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
@@ -154,7 +110,7 @@
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             state.pauseTiming();
-            final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
             state.resumeTiming();
 
             StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
@@ -169,7 +125,7 @@
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             state.pauseTiming();
-            final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
             state.resumeTiming();
 
             StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
@@ -184,7 +140,7 @@
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             state.pauseTiming();
-            final CharSequence text = generateRandomParagraph(WORD_LENGTH, STYLE_TEXT);
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT);
             state.resumeTiming();
 
             StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
@@ -200,7 +156,7 @@
         while (state.keepRunning()) {
             state.pauseTiming();
             final MeasuredText text = new MeasuredText.Builder(
-                    generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
                     .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
                     .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
                     .build();
@@ -219,7 +175,7 @@
         while (state.keepRunning()) {
             state.pauseTiming();
             final MeasuredText text = new MeasuredText.Builder(
-                    generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
                     .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
                     .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
                     .build();
@@ -238,7 +194,7 @@
         while (state.keepRunning()) {
             state.pauseTiming();
             final MeasuredText text = new MeasuredText.Builder(
-                    generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
                     .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
                     .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
                     .build();
@@ -257,7 +213,7 @@
         while (state.keepRunning()) {
             state.pauseTiming();
             final MeasuredText text = new MeasuredText.Builder(
-                    generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
                     .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
                     .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
                     .build();
@@ -276,7 +232,7 @@
         while (state.keepRunning()) {
             state.pauseTiming();
             final MeasuredText text = new MeasuredText.Builder(
-                    generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
+                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT)
                     .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
                     .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
                     .build();
@@ -292,7 +248,7 @@
     @Test
     public void testDraw_FixedText_NoStyled() {
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
+        final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
         final RenderNode node = RenderNode.create("benchmark", null);
         while (state.keepRunning()) {
             state.pauseTiming();
@@ -311,7 +267,7 @@
         final RenderNode node = RenderNode.create("benchmark", null);
         while (state.keepRunning()) {
             state.pauseTiming();
-            final CharSequence text = generateRandomParagraph(WORD_LENGTH, STYLE_TEXT);
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT);
             final StaticLayout layout =
                     StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
             final DisplayListCanvas c = node.start(1200, 200);
@@ -327,7 +283,7 @@
         final RenderNode node = RenderNode.create("benchmark", null);
         while (state.keepRunning()) {
             state.pauseTiming();
-            final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
             final StaticLayout layout =
                     StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
             final DisplayListCanvas c = node.start(1200, 200);
@@ -343,7 +299,7 @@
         final RenderNode node = RenderNode.create("benchmark", null);
         while (state.keepRunning()) {
             state.pauseTiming();
-            final CharSequence text = generateRandomParagraph(WORD_LENGTH, STYLE_TEXT);
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT);
             final StaticLayout layout =
                     StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
             final DisplayListCanvas c = node.start(1200, 200);
@@ -360,7 +316,7 @@
         final RenderNode node = RenderNode.create("benchmark", null);
         while (state.keepRunning()) {
             state.pauseTiming();
-            final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
             final StaticLayout layout =
                     StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
             final DisplayListCanvas c = node.start(1200, 200);
@@ -378,7 +334,7 @@
         while (state.keepRunning()) {
             state.pauseTiming();
             final MeasuredText text = new MeasuredText.Builder(
-                    generateRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT).build();
+                    mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT).build();
             final StaticLayout layout =
                     StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
             final DisplayListCanvas c = node.start(1200, 200);
@@ -395,7 +351,7 @@
         while (state.keepRunning()) {
             state.pauseTiming();
             final MeasuredText text = new MeasuredText.Builder(
-                    generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT).build();
+                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT).build();
             final StaticLayout layout =
                     StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
             final DisplayListCanvas c = node.start(1200, 200);
@@ -412,7 +368,7 @@
         while (state.keepRunning()) {
             state.pauseTiming();
             final MeasuredText text = new MeasuredText.Builder(
-                    generateRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT).build();
+                    mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT).build();
             final StaticLayout layout =
                     StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
             final DisplayListCanvas c = node.start(1200, 200);
@@ -430,7 +386,7 @@
         while (state.keepRunning()) {
             state.pauseTiming();
             final MeasuredText text = new MeasuredText.Builder(
-                    generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT).build();
+                    mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT).build();
             final StaticLayout layout =
                     StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
             final DisplayListCanvas c = node.start(1200, 200);
diff --git a/apct-tests/perftests/core/src/android/text/TextPerfUtils.java b/apct-tests/perftests/core/src/android/text/TextPerfUtils.java
new file mode 100644
index 0000000..dccb34b
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/text/TextPerfUtils.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2018 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 android.text;
+
+import static android.text.TextDirectionHeuristics.LTR;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import android.content.res.ColorStateList;
+import android.graphics.Canvas;
+import android.graphics.Typeface;
+import android.text.Layout;
+import android.text.style.TextAppearanceSpan;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.CharBuffer;
+import java.util.Random;
+
+public class TextPerfUtils {
+
+    private static final int PARA_LENGTH = 500;  // Number of characters in a paragraph.
+
+    private Random mRandom = new Random(0);
+
+    private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+    private static final int ALPHABET_LENGTH = ALPHABET.length();
+
+    private static final ColorStateList TEXT_COLOR = ColorStateList.valueOf(0x00000000);
+    private static final String[] FAMILIES = { "sans-serif", "serif", "monospace" };
+    private static final int[] STYLES = {
+            Typeface.NORMAL, Typeface.BOLD, Typeface.ITALIC, Typeface.BOLD_ITALIC
+    };
+
+    private final char[] mBuffer = new char[PARA_LENGTH];
+
+    public void resetRandom(long seed) {
+        mRandom = new Random(seed);
+    }
+
+    public CharSequence nextRandomParagraph(int wordLen, boolean applyRandomStyle) {
+        for (int i = 0; i < PARA_LENGTH; i++) {
+            if (i % (wordLen + 1) == wordLen) {
+                mBuffer[i] = ' ';
+            } else {
+                mBuffer[i] = ALPHABET.charAt(mRandom.nextInt(ALPHABET_LENGTH));
+            }
+        }
+
+        CharSequence cs = CharBuffer.wrap(mBuffer);
+        if (!applyRandomStyle) {
+            return cs;
+        }
+
+        SpannableStringBuilder ssb = new SpannableStringBuilder(cs);
+        for (int i = 0; i < ssb.length(); i += wordLen) {
+            final int spanStart = i;
+            final int spanEnd = (i + wordLen) > ssb.length() ? ssb.length() : i + wordLen;
+
+            final TextAppearanceSpan span = new TextAppearanceSpan(
+                  FAMILIES[mRandom.nextInt(FAMILIES.length)],
+                  STYLES[mRandom.nextInt(STYLES.length)],
+                  24 + mRandom.nextInt(32),  // text size. min 24 max 56
+                  TEXT_COLOR, TEXT_COLOR);
+
+            ssb.setSpan(span, spanStart, spanEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        }
+        return ssb;
+    }
+}
diff --git a/api/system-current.txt b/api/system-current.txt
index ed3897c..396ff1a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -2603,12 +2603,17 @@
     method public void onStatusChange();
   }
 
+  public static abstract class AudioPolicy.AudioPolicyVolumeCallback {
+    method public void onVolumeAdjustment(int);
+  }
+
   public static class AudioPolicy.Builder {
     ctor public AudioPolicy.Builder(android.content.Context);
     method public android.media.audiopolicy.AudioPolicy.Builder addMix(android.media.audiopolicy.AudioMix) throws java.lang.IllegalArgumentException;
     method public android.media.audiopolicy.AudioPolicy build();
     method public void setAudioPolicyFocusListener(android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener);
     method public void setAudioPolicyStatusListener(android.media.audiopolicy.AudioPolicy.AudioPolicyStatusListener);
+    method public android.media.audiopolicy.AudioPolicy.Builder setAudioPolicyVolumeCallback(android.media.audiopolicy.AudioPolicy.AudioPolicyVolumeCallback);
     method public android.media.audiopolicy.AudioPolicy.Builder setIsAudioFocusPolicy(boolean);
     method public android.media.audiopolicy.AudioPolicy.Builder setLooper(android.os.Looper) throws java.lang.IllegalArgumentException;
   }
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 7b27fc0..b3c8737 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -7057,7 +7057,7 @@
      *
      * @param name The name of the desired item.
      *
-     * @return the value of an item that previously added with putExtra()
+     * @return the value of an item previously added with putExtra(),
      * or null if none was found.
      *
      * @deprecated
@@ -7075,7 +7075,7 @@
      * @param defaultValue the value to be returned if no value of the desired
      * type is stored with the given name.
      *
-     * @return the value of an item that previously added with putExtra()
+     * @return the value of an item previously added with putExtra(),
      * or the default value if none was found.
      *
      * @see #putExtra(String, boolean)
@@ -7092,7 +7092,7 @@
      * @param defaultValue the value to be returned if no value of the desired
      * type is stored with the given name.
      *
-     * @return the value of an item that previously added with putExtra()
+     * @return the value of an item previously added with putExtra(),
      * or the default value if none was found.
      *
      * @see #putExtra(String, byte)
@@ -7109,7 +7109,7 @@
      * @param defaultValue the value to be returned if no value of the desired
      * type is stored with the given name.
      *
-     * @return the value of an item that previously added with putExtra()
+     * @return the value of an item previously added with putExtra(),
      * or the default value if none was found.
      *
      * @see #putExtra(String, short)
@@ -7126,7 +7126,7 @@
      * @param defaultValue the value to be returned if no value of the desired
      * type is stored with the given name.
      *
-     * @return the value of an item that previously added with putExtra()
+     * @return the value of an item previously added with putExtra(),
      * or the default value if none was found.
      *
      * @see #putExtra(String, char)
@@ -7143,7 +7143,7 @@
      * @param defaultValue the value to be returned if no value of the desired
      * type is stored with the given name.
      *
-     * @return the value of an item that previously added with putExtra()
+     * @return the value of an item previously added with putExtra(),
      * or the default value if none was found.
      *
      * @see #putExtra(String, int)
@@ -7160,7 +7160,7 @@
      * @param defaultValue the value to be returned if no value of the desired
      * type is stored with the given name.
      *
-     * @return the value of an item that previously added with putExtra()
+     * @return the value of an item previously added with putExtra(),
      * or the default value if none was found.
      *
      * @see #putExtra(String, long)
@@ -7177,7 +7177,7 @@
      * @param defaultValue the value to be returned if no value of the desired
      * type is stored with the given name.
      *
-     * @return the value of an item that previously added with putExtra(),
+     * @return the value of an item previously added with putExtra(),
      * or the default value if no such item is present
      *
      * @see #putExtra(String, float)
@@ -7194,7 +7194,7 @@
      * @param defaultValue the value to be returned if no value of the desired
      * type is stored with the given name.
      *
-     * @return the value of an item that previously added with putExtra()
+     * @return the value of an item previously added with putExtra(),
      * or the default value if none was found.
      *
      * @see #putExtra(String, double)
@@ -7209,7 +7209,7 @@
      *
      * @param name The name of the desired item.
      *
-     * @return the value of an item that previously added with putExtra()
+     * @return the value of an item previously added with putExtra(),
      * or null if no String value was found.
      *
      * @see #putExtra(String, String)
@@ -7223,7 +7223,7 @@
      *
      * @param name The name of the desired item.
      *
-     * @return the value of an item that previously added with putExtra()
+     * @return the value of an item previously added with putExtra(),
      * or null if no CharSequence value was found.
      *
      * @see #putExtra(String, CharSequence)
@@ -7237,7 +7237,7 @@
      *
      * @param name The name of the desired item.
      *
-     * @return the value of an item that previously added with putExtra()
+     * @return the value of an item previously added with putExtra(),
      * or null if no Parcelable value was found.
      *
      * @see #putExtra(String, Parcelable)
@@ -7251,7 +7251,7 @@
      *
      * @param name The name of the desired item.
      *
-     * @return the value of an item that previously added with putExtra()
+     * @return the value of an item previously added with putExtra(),
      * or null if no Parcelable[] value was found.
      *
      * @see #putExtra(String, Parcelable[])
@@ -7265,8 +7265,9 @@
      *
      * @param name The name of the desired item.
      *
-     * @return the value of an item that previously added with putExtra()
-     * or null if no ArrayList<Parcelable> value was found.
+     * @return the value of an item previously added with
+     * putParcelableArrayListExtra(), or null if no
+     * ArrayList<Parcelable> value was found.
      *
      * @see #putParcelableArrayListExtra(String, ArrayList)
      */
@@ -7279,7 +7280,7 @@
      *
      * @param name The name of the desired item.
      *
-     * @return the value of an item that previously added with putExtra()
+     * @return the value of an item previously added with putExtra(),
      * or null if no Serializable value was found.
      *
      * @see #putExtra(String, Serializable)
@@ -7293,8 +7294,9 @@
      *
      * @param name The name of the desired item.
      *
-     * @return the value of an item that previously added with putExtra()
-     * or null if no ArrayList<Integer> value was found.
+     * @return the value of an item previously added with
+     * putIntegerArrayListExtra(), or null if no
+     * ArrayList<Integer> value was found.
      *
      * @see #putIntegerArrayListExtra(String, ArrayList)
      */
@@ -7307,8 +7309,9 @@
      *
      * @param name The name of the desired item.
      *
-     * @return the value of an item that previously added with putExtra()
-     * or null if no ArrayList<String> value was found.
+     * @return the value of an item previously added with
+     * putStringArrayListExtra(), or null if no
+     * ArrayList<String> value was found.
      *
      * @see #putStringArrayListExtra(String, ArrayList)
      */
@@ -7321,8 +7324,9 @@
      *
      * @param name The name of the desired item.
      *
-     * @return the value of an item that previously added with putExtra()
-     * or null if no ArrayList<CharSequence> value was found.
+     * @return the value of an item previously added with
+     * putCharSequenceArrayListExtra, or null if no
+     * ArrayList<CharSequence> value was found.
      *
      * @see #putCharSequenceArrayListExtra(String, ArrayList)
      */
@@ -7335,7 +7339,7 @@
      *
      * @param name The name of the desired item.
      *
-     * @return the value of an item that previously added with putExtra()
+     * @return the value of an item previously added with putExtra(),
      * or null if no boolean array value was found.
      *
      * @see #putExtra(String, boolean[])
@@ -7349,7 +7353,7 @@
      *
      * @param name The name of the desired item.
      *
-     * @return the value of an item that previously added with putExtra()
+     * @return the value of an item previously added with putExtra(),
      * or null if no byte array value was found.
      *
      * @see #putExtra(String, byte[])
@@ -7363,7 +7367,7 @@
      *
      * @param name The name of the desired item.
      *
-     * @return the value of an item that previously added with putExtra()
+     * @return the value of an item previously added with putExtra(),
      * or null if no short array value was found.
      *
      * @see #putExtra(String, short[])
@@ -7377,7 +7381,7 @@
      *
      * @param name The name of the desired item.
      *
-     * @return the value of an item that previously added with putExtra()
+     * @return the value of an item previously added with putExtra(),
      * or null if no char array value was found.
      *
      * @see #putExtra(String, char[])
@@ -7391,7 +7395,7 @@
      *
      * @param name The name of the desired item.
      *
-     * @return the value of an item that previously added with putExtra()
+     * @return the value of an item previously added with putExtra(),
      * or null if no int array value was found.
      *
      * @see #putExtra(String, int[])
@@ -7405,7 +7409,7 @@
      *
      * @param name The name of the desired item.
      *
-     * @return the value of an item that previously added with putExtra()
+     * @return the value of an item previously added with putExtra(),
      * or null if no long array value was found.
      *
      * @see #putExtra(String, long[])
@@ -7419,7 +7423,7 @@
      *
      * @param name The name of the desired item.
      *
-     * @return the value of an item that previously added with putExtra()
+     * @return the value of an item previously added with putExtra(),
      * or null if no float array value was found.
      *
      * @see #putExtra(String, float[])
@@ -7433,7 +7437,7 @@
      *
      * @param name The name of the desired item.
      *
-     * @return the value of an item that previously added with putExtra()
+     * @return the value of an item previously added with putExtra(),
      * or null if no double array value was found.
      *
      * @see #putExtra(String, double[])
@@ -7447,7 +7451,7 @@
      *
      * @param name The name of the desired item.
      *
-     * @return the value of an item that previously added with putExtra()
+     * @return the value of an item previously added with putExtra(),
      * or null if no String array value was found.
      *
      * @see #putExtra(String, String[])
@@ -7461,7 +7465,7 @@
      *
      * @param name The name of the desired item.
      *
-     * @return the value of an item that previously added with putExtra()
+     * @return the value of an item previously added with putExtra(),
      * or null if no CharSequence array value was found.
      *
      * @see #putExtra(String, CharSequence[])
@@ -7475,7 +7479,7 @@
      *
      * @param name The name of the desired item.
      *
-     * @return the value of an item that previously added with putExtra()
+     * @return the value of an item previously added with putExtra(),
      * or null if no Bundle value was found.
      *
      * @see #putExtra(String, Bundle)
@@ -7489,7 +7493,7 @@
      *
      * @param name The name of the desired item.
      *
-     * @return the value of an item that previously added with putExtra()
+     * @return the value of an item previously added with putExtra(),
      * or null if no IBinder value was found.
      *
      * @see #putExtra(String, IBinder)
@@ -7509,7 +7513,7 @@
      * @param defaultValue The default value to return in case no item is
      * associated with the key 'name'
      *
-     * @return the value of an item that previously added with putExtra()
+     * @return the value of an item previously added with putExtra(),
      * or defaultValue if none was found.
      *
      * @see #putExtra
diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index f84ec65..a748f4d 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -28,6 +28,7 @@
 import android.util.LongSparseArray;
 import android.util.SparseIntArray;
 
+import dalvik.annotation.optimization.FastNative;
 import dalvik.system.CloseGuard;
 
 /**
@@ -62,28 +63,43 @@
     private static native void nativeDispose(long windowPtr);
     private static native void nativeWriteToParcel(long windowPtr, Parcel parcel);
 
-    private static native void nativeClear(long windowPtr);
-
-    private static native int nativeGetNumRows(long windowPtr);
-    private static native boolean nativeSetNumColumns(long windowPtr, int columnNum);
-    private static native boolean nativeAllocRow(long windowPtr);
-    private static native void nativeFreeLastRow(long windowPtr);
-
-    private static native int nativeGetType(long windowPtr, int row, int column);
+    private static native String nativeGetName(long windowPtr);
     private static native byte[] nativeGetBlob(long windowPtr, int row, int column);
     private static native String nativeGetString(long windowPtr, int row, int column);
-    private static native long nativeGetLong(long windowPtr, int row, int column);
-    private static native double nativeGetDouble(long windowPtr, int row, int column);
     private static native void nativeCopyStringToBuffer(long windowPtr, int row, int column,
             CharArrayBuffer buffer);
-
     private static native boolean nativePutBlob(long windowPtr, byte[] value, int row, int column);
-    private static native boolean nativePutString(long windowPtr, String value, int row, int column);
+    private static native boolean nativePutString(long windowPtr, String value,
+            int row, int column);
+
+    // Below native methods don't do unconstrained work, so are FastNative for performance
+
+    @FastNative
+    private static native void nativeClear(long windowPtr);
+
+    @FastNative
+    private static native int nativeGetNumRows(long windowPtr);
+    @FastNative
+    private static native boolean nativeSetNumColumns(long windowPtr, int columnNum);
+    @FastNative
+    private static native boolean nativeAllocRow(long windowPtr);
+    @FastNative
+    private static native void nativeFreeLastRow(long windowPtr);
+
+    @FastNative
+    private static native int nativeGetType(long windowPtr, int row, int column);
+    @FastNative
+    private static native long nativeGetLong(long windowPtr, int row, int column);
+    @FastNative
+    private static native double nativeGetDouble(long windowPtr, int row, int column);
+
+    @FastNative
     private static native boolean nativePutLong(long windowPtr, long value, int row, int column);
+    @FastNative
     private static native boolean nativePutDouble(long windowPtr, double value, int row, int column);
+    @FastNative
     private static native boolean nativePutNull(long windowPtr, int row, int column);
 
-    private static native String nativeGetName(long windowPtr);
 
     /**
      * Creates a new empty cursor window and gives it a name.
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index 7fb0c89..7297426 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -22,7 +22,9 @@
 
 /**
  * Class representing a sensor. Use {@link SensorManager#getSensorList} to get
- * the list of available Sensors.
+ * the list of available sensors. For more information about Android sensors,
+ * read the
+ * <a href="/guide/topics/sensors/sensors_motion.html">Motion Sensors guide</a>.</p>
  *
  * @see SensorManager
  * @see SensorEventListener
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index 13b9206..2306e5f 100644
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -61,6 +61,7 @@
  * @attr ref android.R.styleable#KeyboardView_keyBackground
  * @attr ref android.R.styleable#KeyboardView_keyPreviewLayout
  * @attr ref android.R.styleable#KeyboardView_keyPreviewOffset
+ * @attr ref android.R.styleable#KeyboardView_keyPreviewHeight
  * @attr ref android.R.styleable#KeyboardView_labelTextSize
  * @attr ref android.R.styleable#KeyboardView_keyTextSize
  * @attr ref android.R.styleable#KeyboardView_keyTextColor
diff --git a/core/java/android/os/BatteryManagerInternal.java b/core/java/android/os/BatteryManagerInternal.java
index f3a95b9..a86237d 100644
--- a/core/java/android/os/BatteryManagerInternal.java
+++ b/core/java/android/os/BatteryManagerInternal.java
@@ -24,26 +24,63 @@
 public abstract class BatteryManagerInternal {
     /**
      * Returns true if the device is plugged into any of the specified plug types.
+     *
+     * This is a simple accessor that's safe to be called from any locks, but internally it may
+     * wait on the battery service lock.
      */
     public abstract boolean isPowered(int plugTypeSet);
 
     /**
      * Returns the current plug type.
+     *
+     * This is a simple accessor that's safe to be called from any locks, but internally it may
+     * wait on the battery service lock.
      */
     public abstract int getPlugType();
 
     /**
      * Returns battery level as a percentage.
+     *
+     * This is a simple accessor that's safe to be called from any locks, but internally it may
+     * wait on the battery service lock.
      */
     public abstract int getBatteryLevel();
 
     /**
+     * Instantaneous battery capacity in uA-h, as defined in the HealthInfo HAL struct.
+     * Please note apparently it could be bigger than {@link #getBatteryFullCharge}.
+     *
+     * This is a simple accessor that's safe to be called from any locks, but internally it may
+     * wait on the battery service lock.
+     *
+     * @see android.hardware.health.V1_0.HealthInfo#batteryChargeCounter
+     */
+    public abstract int getBatteryChargeCounter();
+
+    /**
+     * Battery charge value when it is considered to be "full" in uA-h , as defined in the
+     * HealthInfo HAL struct.
+     *
+     * This is a simple accessor that's safe to be called from any locks, but internally it may
+     * wait on the battery service lock.
+     *
+     * @see android.hardware.health.V1_0.HealthInfo#batteryFullCharge
+     */
+    public abstract int getBatteryFullCharge();
+
+    /**
      * Returns whether we currently consider the battery level to be low.
+     *
+     * This is a simple accessor that's safe to be called from any locks, but internally it may
+     * wait on the battery service lock.
      */
     public abstract boolean getBatteryLevelLow();
 
     /**
      * Returns a non-zero value if an unsupported charger is attached.
+     *
+     * This is a simple accessor that's safe to be called from any locks, but internally it may
+     * wait on the battery service lock.
      */
     public abstract int getInvalidCharger();
 }
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index 45fbf6f..aafcf44 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -672,6 +672,13 @@
         return width;
     }
 
+    /**
+     * This only works if the MeasuredParagraph is computed with buildForStaticLayout.
+     */
+    @IntRange(from = 0) int getMemoryUsage() {
+        return nGetMemoryUsage(mNativePtr);
+    }
+
     private static native /* Non Zero */ long nInitBuilder();
 
     /**
@@ -718,4 +725,7 @@
 
     @CriticalNative
     private static native /* Non Zero */ long nGetReleaseFunc();
+
+    @CriticalNative
+    private static native int nGetMemoryUsage(/* Non Zero */ long nativePtr);
 }
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index ff23395..bb7a9e0 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -333,6 +333,20 @@
         return getMeasuredParagraph(paraIndex).getWidth(start - paraStart, end - paraStart);
     }
 
+    /**
+     * Returns the size of native MeasuredText memory usage
+     *
+     * Note that this may not be aculate. Must be used only for testing purposes.
+     * @hide
+     */
+    public int getMemoryUsage() {
+        int r = 0;
+        for (int i = 0; i < getParagraphCount(); ++i) {
+            r += getMeasuredParagraph(i).getMemoryUsage();
+        }
+        return r;
+    }
+
     ///////////////////////////////////////////////////////////////////////////////////////////////
     // Spanned overrides
     //
diff --git a/core/jni/android_database_CursorWindow.cpp b/core/jni/android_database_CursorWindow.cpp
index 0d09b56..86cda44 100644
--- a/core/jni/android_database_CursorWindow.cpp
+++ b/core/jni/android_database_CursorWindow.cpp
@@ -519,8 +519,21 @@
             (void*)nativeDispose },
     { "nativeWriteToParcel", "(JLandroid/os/Parcel;)V",
             (void*)nativeWriteToParcel },
+
     { "nativeGetName", "(J)Ljava/lang/String;",
             (void*)nativeGetName },
+    { "nativeGetBlob", "(JII)[B",
+            (void*)nativeGetBlob },
+    { "nativeGetString", "(JII)Ljava/lang/String;",
+            (void*)nativeGetString },
+    { "nativeCopyStringToBuffer", "(JIILandroid/database/CharArrayBuffer;)V",
+            (void*)nativeCopyStringToBuffer },
+    { "nativePutBlob", "(J[BII)Z",
+            (void*)nativePutBlob },
+    { "nativePutString", "(JLjava/lang/String;II)Z",
+            (void*)nativePutString },
+
+    // ------- @FastNative below here ----------------------
     { "nativeClear", "(J)V",
             (void*)nativeClear },
     { "nativeGetNumRows", "(J)I",
@@ -533,20 +546,11 @@
             (void*)nativeFreeLastRow },
     { "nativeGetType", "(JII)I",
             (void*)nativeGetType },
-    { "nativeGetBlob", "(JII)[B",
-            (void*)nativeGetBlob },
-    { "nativeGetString", "(JII)Ljava/lang/String;",
-            (void*)nativeGetString },
     { "nativeGetLong", "(JII)J",
             (void*)nativeGetLong },
     { "nativeGetDouble", "(JII)D",
             (void*)nativeGetDouble },
-    { "nativeCopyStringToBuffer", "(JIILandroid/database/CharArrayBuffer;)V",
-            (void*)nativeCopyStringToBuffer },
-    { "nativePutBlob", "(J[BII)Z",
-            (void*)nativePutBlob },
-    { "nativePutString", "(JLjava/lang/String;II)Z",
-            (void*)nativePutString },
+
     { "nativePutLong", "(JJII)Z",
             (void*)nativePutLong },
     { "nativePutDouble", "(JDII)Z",
diff --git a/core/jni/android_text_MeasuredParagraph.cpp b/core/jni/android_text_MeasuredParagraph.cpp
index f0e449d..dddddbb 100644
--- a/core/jni/android_text_MeasuredParagraph.cpp
+++ b/core/jni/android_text_MeasuredParagraph.cpp
@@ -115,6 +115,10 @@
     return toJLong(&releaseMeasuredParagraph);
 }
 
+static jint nGetMemoryUsage(jlong ptr) {
+    return static_cast<jint>(toMeasuredParagraph(ptr)->getMemoryUsage());
+}
+
 static const JNINativeMethod gMethods[] = {
     // MeasuredParagraphBuilder native functions.
     {"nInitBuilder", "()J", (void*) nInitBuilder},
@@ -126,6 +130,7 @@
     // MeasuredParagraph native functions.
     {"nGetWidth", "(JII)F", (void*) nGetWidth},  // Critical Natives
     {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc},  // Critical Natives
+    {"nGetMemoryUsage", "(J)I", (void*) nGetMemoryUsage},  // Critical Native
 };
 
 int register_android_text_MeasuredParagraph(JNIEnv* env) {
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 2ac4063..7533701 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -400,6 +400,18 @@
     public static final int ADJUST_TOGGLE_MUTE = 101;
 
     /** @hide */
+    @IntDef(flag = false, prefix = "ADJUST", value = {
+            ADJUST_RAISE,
+            ADJUST_LOWER,
+            ADJUST_SAME,
+            ADJUST_MUTE,
+            ADJUST_UNMUTE,
+            ADJUST_TOGGLE_MUTE }
+            )
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface VolumeAdjustement {}
+
+    /** @hide */
     public static final String adjustToString(int adj) {
         switch (adj) {
             case ADJUST_RAISE: return "ADJUST_RAISE";
@@ -2989,7 +3001,7 @@
         final IAudioService service = getService();
         try {
             String regId = service.registerAudioPolicy(policy.getConfig(), policy.cb(),
-                    policy.hasFocusListener(), policy.isFocusPolicy());
+                    policy.hasFocusListener(), policy.isFocusPolicy(), policy.isVolumeController());
             if (regId == null) {
                 return ERROR;
             } else {
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 6c65223..88d0a60 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -166,7 +166,8 @@
     boolean isHdmiSystemAudioSupported();
 
     String registerAudioPolicy(in AudioPolicyConfig policyConfig,
-            in IAudioPolicyCallback pcb, boolean hasFocusListener, boolean isFocusPolicy);
+            in IAudioPolicyCallback pcb, boolean hasFocusListener, boolean isFocusPolicy,
+            boolean isVolumeController);
 
     oneway void unregisterAudioPolicyAsync(in IAudioPolicyCallback pcb);
 
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index 7e88c27..d0bba6d 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -89,6 +89,8 @@
 
     private AudioPolicyFocusListener mFocusListener;
 
+    private final AudioPolicyVolumeCallback mVolCb;
+
     private Context mContext;
 
     private AudioPolicyConfig mConfig;
@@ -99,12 +101,15 @@
     public boolean hasFocusListener() { return mFocusListener != null; }
     /** @hide */
     public boolean isFocusPolicy() { return mIsFocusPolicy; }
+    /** @hide */
+    public boolean isVolumeController() { return mVolCb != null; }
 
     /**
      * The parameter is guaranteed non-null through the Builder
      */
     private AudioPolicy(AudioPolicyConfig config, Context context, Looper looper,
-            AudioPolicyFocusListener fl, AudioPolicyStatusListener sl, boolean isFocusPolicy) {
+            AudioPolicyFocusListener fl, AudioPolicyStatusListener sl, boolean isFocusPolicy,
+            AudioPolicyVolumeCallback vc) {
         mConfig = config;
         mStatus = POLICY_STATUS_UNREGISTERED;
         mContext = context;
@@ -120,6 +125,7 @@
         mFocusListener = fl;
         mStatusListener = sl;
         mIsFocusPolicy = isFocusPolicy;
+        mVolCb = vc;
     }
 
     /**
@@ -134,6 +140,7 @@
         private AudioPolicyFocusListener mFocusListener;
         private AudioPolicyStatusListener mStatusListener;
         private boolean mIsFocusPolicy = false;
+        private AudioPolicyVolumeCallback mVolCb;
 
         /**
          * Constructs a new Builder with no audio mixes.
@@ -208,6 +215,22 @@
             mStatusListener = l;
         }
 
+        @SystemApi
+        /**
+         * Sets the callback to receive all volume key-related events.
+         * The callback will only be called if the device is configured to handle volume events
+         * in the PhoneWindowManager (see config_handleVolumeKeysInWindowManager)
+         * @param vc
+         * @return the same Builder instance.
+         */
+        public Builder setAudioPolicyVolumeCallback(@NonNull AudioPolicyVolumeCallback vc) {
+            if (vc == null) {
+                throw new IllegalArgumentException("Invalid null volume callback");
+            }
+            mVolCb = vc;
+            return this;
+        }
+
         /**
          * Combines all of the attributes that have been set on this {@code Builder} and returns a
          * new {@link AudioPolicy} object.
@@ -229,7 +252,7 @@
                         + "an AudioPolicyFocusListener");
             }
             return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext, mLooper,
-                    mFocusListener, mStatusListener, mIsFocusPolicy);
+                    mFocusListener, mStatusListener, mIsFocusPolicy, mVolCb);
         }
     }
 
@@ -455,6 +478,23 @@
         public void onAudioFocusAbandon(AudioFocusInfo afi) {}
     }
 
+    @SystemApi
+    /**
+     * Callback class to receive volume change-related events.
+     * See {@link #Builder.setAudioPolicyVolumeCallback(AudioPolicyCallback)} to configure the
+     * {@link AudioPolicy} to receive those events.
+     *
+     */
+    public static abstract class AudioPolicyVolumeCallback {
+        /** @hide */
+        public AudioPolicyVolumeCallback() {}
+        /**
+         * Called when volume key-related changes are triggered, on the key down event.
+         * @param adjustement the type of volume adjustment for the key.
+         */
+        public void onVolumeAdjustment(@AudioManager.VolumeAdjustement int adjustement) {}
+    }
+
     private void onPolicyStatusChange() {
         AudioPolicyStatusListener l;
         synchronized (mLock) {
@@ -517,6 +557,13 @@
                 }
             }
         }
+
+        public void notifyVolumeAdjust(int adjustment) {
+            sendMsg(MSG_VOL_ADJUST, null /* ignored */, adjustment);
+            if (DEBUG) {
+                Log.v(TAG, "notifyVolumeAdjust: " + adjustment);
+            }
+        }
     };
 
     //==================================================
@@ -528,6 +575,7 @@
     private final static int MSG_MIX_STATE_UPDATE = 3;
     private final static int MSG_FOCUS_REQUEST = 4;
     private final static int MSG_FOCUS_ABANDON = 5;
+    private final static int MSG_VOL_ADJUST = 6;
 
     private class EventHandler extends Handler {
         public EventHandler(AudioPolicy ap, Looper looper) {
@@ -571,6 +619,12 @@
                         Log.e(TAG, "Invalid null focus listener for focus abandon event");
                     }
                     break;
+                case MSG_VOL_ADJUST:
+                    if (mVolCb != null) {
+                        mVolCb.onVolumeAdjustment(msg.arg1);
+                    } else { // should never be null, but don't crash
+                        Log.e(TAG, "Invalid null volume event");
+                    }
                 default:
                     Log.e(TAG, "Unknown event " + msg.what);
             }
diff --git a/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl b/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl
index 86abbb4..107e7cd 100644
--- a/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl
+++ b/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl
@@ -31,4 +31,7 @@
 
     // callback for mix activity status update
     void notifyMixStateUpdate(in String regId, int state);
+
+    // callback for volume events
+    void notifyVolumeAdjust(int adjustment);
 }
diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp
index 42ebf6a..ff854c5 100644
--- a/media/jni/android_media_MediaMetadataRetriever.cpp
+++ b/media/jni/android_media_MediaMetadataRetriever.cpp
@@ -68,17 +68,26 @@
     }
 }
 
-static MediaMetadataRetriever* getRetriever(JNIEnv* env, jobject thiz)
+static sp<MediaMetadataRetriever> getRetriever(JNIEnv* env, jobject thiz)
 {
     // No lock is needed, since it is called internally by other methods that are protected
     MediaMetadataRetriever* retriever = (MediaMetadataRetriever*) env->GetLongField(thiz, fields.context);
     return retriever;
 }
 
-static void setRetriever(JNIEnv* env, jobject thiz, MediaMetadataRetriever* retriever)
+static void setRetriever(JNIEnv* env, jobject thiz, const sp<MediaMetadataRetriever> &retriever)
 {
     // No lock is needed, since it is called internally by other methods that are protected
-    env->SetLongField(thiz, fields.context, (jlong) retriever);
+
+    if (retriever != NULL) {
+        retriever->incStrong(thiz);
+    }
+    sp<MediaMetadataRetriever> old = getRetriever(env, thiz);
+    if (old != NULL) {
+        old->decStrong(thiz);
+    }
+
+    env->SetLongField(thiz, fields.context, (jlong) retriever.get());
 }
 
 static void
@@ -87,7 +96,7 @@
         jobjectArray keys, jobjectArray values) {
 
     ALOGV("setDataSource");
-    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
+    sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz);
     if (retriever == 0) {
         jniThrowException(
                 env,
@@ -146,7 +155,7 @@
 static void android_media_MediaMetadataRetriever_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
 {
     ALOGV("setDataSource");
-    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
+    sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz);
     if (retriever == 0) {
         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
         return;
@@ -175,7 +184,7 @@
 static void android_media_MediaMetadataRetriever_setDataSourceCallback(JNIEnv *env, jobject thiz, jobject dataSource)
 {
     ALOGV("setDataSourceCallback");
-    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
+    sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz);
     if (retriever == 0) {
         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
         return;
@@ -325,7 +334,7 @@
 {
     ALOGV("getFrameAtTime: %lld us option: %d dst width: %d heigh: %d",
             (long long)timeUs, option, dst_width, dst_height);
-    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
+    sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz);
     if (retriever == 0) {
         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
         return NULL;
@@ -349,7 +358,7 @@
         JNIEnv *env, jobject thiz, jint index)
 {
     ALOGV("getImageAtIndex: index %d", index);
-    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
+    sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz);
     if (retriever == 0) {
         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
         return NULL;
@@ -373,7 +382,7 @@
         JNIEnv *env, jobject thiz, jint frameIndex, jint numFrames)
 {
     ALOGV("getFrameAtIndex: frameIndex %d, numFrames %d", frameIndex, numFrames);
-    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
+    sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz);
     if (retriever == 0) {
         jniThrowException(env,
                 "java/lang/IllegalStateException", "No retriever available");
@@ -411,7 +420,7 @@
         JNIEnv *env, jobject thiz, jint pictureType)
 {
     ALOGV("getEmbeddedPicture: %d", pictureType);
-    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
+    sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz);
     if (retriever == 0) {
         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
         return NULL;
@@ -446,7 +455,7 @@
 static jobject android_media_MediaMetadataRetriever_extractMetadata(JNIEnv *env, jobject thiz, jint keyCode)
 {
     ALOGV("extractMetadata");
-    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
+    sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz);
     if (retriever == 0) {
         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
         return NULL;
@@ -464,9 +473,7 @@
 {
     ALOGV("release");
     Mutex::Autolock lock(sLock);
-    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
-    delete retriever;
-    setRetriever(env, thiz, (MediaMetadataRetriever*) 0);
+    setRetriever(env, thiz, NULL);
 }
 
 static void android_media_MediaMetadataRetriever_native_finalize(JNIEnv *env, jobject thiz)
@@ -533,7 +540,7 @@
 static void android_media_MediaMetadataRetriever_native_setup(JNIEnv *env, jobject thiz)
 {
     ALOGV("native_setup");
-    MediaMetadataRetriever* retriever = new MediaMetadataRetriever();
+    sp<MediaMetadataRetriever> retriever = new MediaMetadataRetriever();
     if (retriever == 0) {
         jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
         return;
diff --git a/packages/SystemUI/shared/Android.mk b/packages/SystemUI/shared/Android.mk
index 5f75f7d..21b0ed8 100644
--- a/packages/SystemUI/shared/Android.mk
+++ b/packages/SystemUI/shared/Android.mk
@@ -30,7 +30,7 @@
 
 include $(CLEAR_VARS)
 
-LOCAL_PACKAGE_NAME := SystemUISharedLib
+LOCAL_PACKAGE_NAME := SysUISharedLib
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_JAVA_LIBRARIES := SystemUISharedLib
@@ -39,4 +39,4 @@
 
 include $(BUILD_PACKAGE)
 
-include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 8666b0c..1ae06d7 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -119,18 +119,10 @@
         addView(mBatteryIconView, mlp);
 
         updateShowPercent();
-
-        Context dualToneDarkTheme = new ContextThemeWrapper(context,
-                Utils.getThemeAttr(context, R.attr.darkIconTheme));
-        Context dualToneLightTheme = new ContextThemeWrapper(context,
-                Utils.getThemeAttr(context, R.attr.lightIconTheme));
-        mDarkModeBackgroundColor = Utils.getColorAttr(dualToneDarkTheme, R.attr.backgroundColor);
-        mDarkModeFillColor = Utils.getColorAttr(dualToneDarkTheme, R.attr.fillColor);
-        mLightModeBackgroundColor = Utils.getColorAttr(dualToneLightTheme, R.attr.backgroundColor);
-        mLightModeFillColor = Utils.getColorAttr(dualToneLightTheme, R.attr.fillColor);
-
+        setColorsFromContext(context);
         // Init to not dark at all.
         onDarkChanged(new Rect(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT);
+
         mUserTracker = new CurrentUserTracker(mContext) {
             @Override
             public void onUserSwitched(int newUserId) {
@@ -148,6 +140,21 @@
         updateShowPercent();
     }
 
+    public void setColorsFromContext(Context context) {
+        if (context == null) {
+            return;
+        }
+
+        Context dualToneDarkTheme = new ContextThemeWrapper(context,
+                Utils.getThemeAttr(context, R.attr.darkIconTheme));
+        Context dualToneLightTheme = new ContextThemeWrapper(context,
+                Utils.getThemeAttr(context, R.attr.lightIconTheme));
+        mDarkModeBackgroundColor = Utils.getColorAttr(dualToneDarkTheme, R.attr.backgroundColor);
+        mDarkModeFillColor = Utils.getColorAttr(dualToneDarkTheme, R.attr.fillColor);
+        mLightModeBackgroundColor = Utils.getColorAttr(dualToneLightTheme, R.attr.backgroundColor);
+        mLightModeFillColor = Utils.getColorAttr(dualToneLightTheme, R.attr.fillColor);
+    }
+
     @Override
     public boolean hasOverlappingRendering() {
         return false;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 77768b1..4d7333b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -97,7 +97,6 @@
         mIconManager.setTint(fillColor);
 
         BatteryMeterView battery = findViewById(R.id.battery);
-        battery.setFillColor(Color.WHITE);
         battery.setForceShowPercent(true);
 
         mActivityStarter = Dependency.get(ActivityStarter.class);
@@ -216,6 +215,11 @@
         //host.setHeaderView(mExpandIndicator);
         mHeaderQsPanel.setQSPanelAndHeader(mQsPanel, this);
         mHeaderQsPanel.setHost(host, null /* No customization in header */);
+
+        // Use SystemUI context to get battery meter colors, and let it use the default tint (white)
+        BatteryMeterView battery = findViewById(R.id.battery);
+        battery.setColorsFromContext(mHost.getContext());
+        battery.onDarkChanged(new Rect(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT);
     }
 
     public void setCallback(Callback qsPanelCallback) {
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index ba9883b..8265262 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -835,6 +835,9 @@
                         case "level":
                             mHealthInfo.batteryLevel = Integer.parseInt(value);
                             break;
+                        case "counter":
+                            mHealthInfo.batteryChargeCounter = Integer.parseInt(value);
+                            break;
                         case "temp":
                             mHealthInfo.batteryTemperature = Integer.parseInt(value);
                             break;
@@ -1164,6 +1167,20 @@
         }
 
         @Override
+        public int getBatteryChargeCounter() {
+            synchronized (mLock) {
+                return mHealthInfo.batteryChargeCounter;
+            }
+        }
+
+        @Override
+        public int getBatteryFullCharge() {
+            synchronized (mLock) {
+                return mHealthInfo.batteryFullCharge;
+            }
+        }
+
+        @Override
         public boolean getBatteryLevelLow() {
             synchronized (mLock) {
                 return mBatteryLevelLow;
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 732ac66..219facd 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -34,6 +34,7 @@
 2731 power_soft_sleep_requested (savedwaketimems|2)
 # Power save state has changed. See BatterySaverController.java for the details.
 2739 battery_saver_mode (prevOffOrOn|1|5),(nowOffOrOn|1|5),(interactive|1|5),(features|3|5)
+27390 battery_saving_stats (batterySaver|1|5),(interactive|1|5),(doze|1|5),(delta_duration|2|3),(delta_battery_drain|1|6),(total_duration|2|3),(total_battery_drain|1|6)
 
 #
 # Leave IDs through 2740 for more power logs (2730 used by battery_discharge above)
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index fffb4b5..7e952f1e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -14853,6 +14853,8 @@
             mUserController.sendUserSwitchBroadcasts(-1, currentUserId);
 
             BinderInternal.nSetBinderProxyCountEnabled(true);
+            //STOPSHIP: Temporary BinderProxy Threshold for b/71353150
+            BinderInternal.nSetBinderProxyCountWatermarks(1500, 1200);
             BinderInternal.setBinderProxyCountCallback(
                     new BinderInternal.BinderProxyLimitListener() {
                         @Override
diff --git a/services/core/java/com/android/server/am/RecentsAnimation.java b/services/core/java/com/android/server/am/RecentsAnimation.java
index f0b27a7..c188ccb 100644
--- a/services/core/java/com/android/server/am/RecentsAnimation.java
+++ b/services/core/java/com/android/server/am/RecentsAnimation.java
@@ -70,55 +70,61 @@
 
     void startRecentsActivity(Intent intent, IRecentsAnimationRunner recentsAnimationRunner,
             ComponentName recentsComponent, int recentsUid) {
+        mWindowManager.deferSurfaceLayout();
+        try {
+            // Cancel the previous recents animation if necessary
+            mWindowManager.cancelRecentsAnimation();
 
-        // Cancel the previous recents animation if necessary
-        mWindowManager.cancelRecentsAnimation();
+            final boolean hasExistingHomeActivity = mStackSupervisor.getHomeActivity() != null;
+            if (!hasExistingHomeActivity) {
+                // No home activity
+                final ActivityOptions opts = ActivityOptions.makeBasic();
+                opts.setLaunchActivityType(ACTIVITY_TYPE_HOME);
+                opts.setAvoidMoveToFront();
+                intent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NO_ANIMATION);
 
-        final boolean hasExistingHomeActivity = mStackSupervisor.getHomeActivity() != null;
-        if (!hasExistingHomeActivity) {
-            // No home activity
-            final ActivityOptions opts = ActivityOptions.makeBasic();
-            opts.setLaunchActivityType(ACTIVITY_TYPE_HOME);
-            opts.setAvoidMoveToFront();
-            intent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NO_ANIMATION);
+                mActivityStartController
+                        .obtainStarter(intent, "startRecentsActivity_noHomeActivity")
+                        .setCallingUid(recentsUid)
+                        .setCallingPackage(recentsComponent.getPackageName())
+                        .setActivityOptions(SafeActivityOptions.fromBundle(opts.toBundle()))
+                        .setMayWait(mUserController.getCurrentUserId())
+                        .execute();
+                mWindowManager.prepareAppTransition(TRANSIT_NONE, false);
 
-            mActivityStartController.obtainStarter(intent, "startRecentsActivity_noHomeActivity")
-                    .setCallingUid(recentsUid)
-                    .setCallingPackage(recentsComponent.getPackageName())
-                    .setActivityOptions(SafeActivityOptions.fromBundle(opts.toBundle()))
-                    .setMayWait(mUserController.getCurrentUserId())
-                    .execute();
-            mWindowManager.prepareAppTransition(TRANSIT_NONE, false);
+                // TODO: Maybe wait for app to draw in this particular case?
+            }
 
-            // TODO: Maybe wait for app to draw in this particular case?
+            final ActivityRecord homeActivity = mStackSupervisor.getHomeActivity();
+            final ActivityDisplay display = homeActivity.getDisplay();
+
+            // Save the initial position of the home activity stack to be restored to after the
+            // animation completes
+            mRestoreHomeBehindStack = hasExistingHomeActivity
+                    ? display.getStackAboveHome()
+                    : null;
+
+            // Move the home activity into place for the animation
+            display.moveHomeStackBehindBottomMostVisibleStack();
+
+            // Mark the home activity as launch-behind to bump its visibility for the
+            // duration of the gesture that is driven by the recents component
+            homeActivity.mLaunchTaskBehind = true;
+
+            // Fetch all the surface controls and pass them to the client to get the animation
+            // started
+            mWindowManager.initializeRecentsAnimation(recentsAnimationRunner, this,
+                    display.mDisplayId);
+
+            // If we updated the launch-behind state, update the visibility of the activities after
+            // we fetch the visible tasks to be controlled by the animation
+            mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, PRESERVE_WINDOWS);
+
+            // Post a timeout for the animation
+            mHandler.postDelayed(mCancelAnimationRunnable, RECENTS_ANIMATION_TIMEOUT);
+        } finally {
+            mWindowManager.continueSurfaceLayout();
         }
-
-        final ActivityRecord homeActivity = mStackSupervisor.getHomeActivity();
-        final ActivityDisplay display = homeActivity.getDisplay();
-
-        // Save the initial position of the home activity stack to be restored to after the
-        // animation completes
-        mRestoreHomeBehindStack = hasExistingHomeActivity
-                ? display.getStackAboveHome()
-                : null;
-
-        // Move the home activity into place for the animation
-        display.moveHomeStackBehindBottomMostVisibleStack();
-
-        // Mark the home activity as launch-behind to bump its visibility for the
-        // duration of the gesture that is driven by the recents component
-        homeActivity.mLaunchTaskBehind = true;
-
-        // Fetch all the surface controls and pass them to the client to get the animation
-        // started
-        mWindowManager.initializeRecentsAnimation(recentsAnimationRunner, this, display.mDisplayId);
-
-        // If we updated the launch-behind state, update the visibility of the activities after we
-        // fetch the visible tasks to be controlled by the animation
-        mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, PRESERVE_WINDOWS);
-
-        // Post a timeout for the animation
-        mHandler.postDelayed(mCancelAnimationRunnable, RECENTS_ANIMATION_TIMEOUT);
     }
 
     @Override
@@ -128,35 +134,40 @@
             if (mWindowManager.getRecentsAnimationController() == null) return;
 
             mWindowManager.inSurfaceTransaction(() -> {
-                mWindowManager.cleanupRecentsAnimation();
+                mWindowManager.deferSurfaceLayout();
+                try {
+                    mWindowManager.cleanupRecentsAnimation();
 
-                // Move the home stack to the front
-                final ActivityRecord homeActivity = mStackSupervisor.getHomeActivity();
-                if (homeActivity == null) {
-                    return;
+                    // Move the home stack to the front
+                    final ActivityRecord homeActivity = mStackSupervisor.getHomeActivity();
+                    if (homeActivity == null) {
+                        return;
+                    }
+
+                    // Restore the launched-behind state
+                    homeActivity.mLaunchTaskBehind = false;
+
+                    if (moveHomeToTop) {
+                        // Bring the home stack to the front
+                        final ActivityStack homeStack = homeActivity.getStack();
+                        homeStack.mNoAnimActivities.add(homeActivity);
+                        homeStack.moveToFront("RecentsAnimation.onAnimationFinished()");
+                    } else {
+                        // Restore the home stack to its previous position
+                        final ActivityDisplay display = homeActivity.getDisplay();
+                        display.moveHomeStackBehindStack(mRestoreHomeBehindStack);
+                    }
+
+                    mWindowManager.prepareAppTransition(TRANSIT_NONE, false);
+                    mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, false);
+                    mStackSupervisor.resumeFocusedStackTopActivityLocked();
+
+                    // No reason to wait for the pausing activity in this case, as the hiding of
+                    // surfaces needs to be done immediately.
+                    mWindowManager.executeAppTransition();
+                } finally {
+                    mWindowManager.continueSurfaceLayout();
                 }
-
-                // Restore the launched-behind state
-                homeActivity.mLaunchTaskBehind = false;
-
-                if (moveHomeToTop) {
-                    // Bring the home stack to the front
-                    final ActivityStack homeStack = homeActivity.getStack();
-                    homeStack.mNoAnimActivities.add(homeActivity);
-                    homeStack.moveToFront("RecentsAnimation.onAnimationFinished()");
-                } else {
-                    // Restore the home stack to its previous position
-                    final ActivityDisplay display = homeActivity.getDisplay();
-                    display.moveHomeStackBehindStack(mRestoreHomeBehindStack);
-                }
-
-                mWindowManager.prepareAppTransition(TRANSIT_NONE, false);
-                mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, false);
-                mStackSupervisor.resumeFocusedStackTopActivityLocked();
-
-                // No reason to wait for the pausing activity in this case, as the hiding of
-                // surfaces needs to be done immediately.
-                mWindowManager.executeAppTransition();
             });
         }
     }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index f6bcb25..f247de7 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1329,8 +1329,20 @@
     /** @see AudioManager#adjustVolume(int, int) */
     public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
             String callingPackage, String caller) {
-        adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage,
-                caller, Binder.getCallingUid());
+        final IAudioPolicyCallback extVolCtlr;
+        synchronized (mExtVolumeControllerLock) {
+            extVolCtlr = mExtVolumeController;
+        }
+        if (extVolCtlr != null) {
+            try {
+                mExtVolumeController.notifyVolumeAdjust(direction);
+            } catch(RemoteException e) {
+                // nothing we can do about this. Do not log error, too much potential for spam
+            }
+        } else {
+            adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage,
+                    caller, Binder.getCallingUid());
+        }
     }
 
     private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
@@ -6964,7 +6976,7 @@
     // Audio policy management
     //==========================================================================================
     public String registerAudioPolicy(AudioPolicyConfig policyConfig, IAudioPolicyCallback pcb,
-            boolean hasFocusListener, boolean isFocusPolicy) {
+            boolean hasFocusListener, boolean isFocusPolicy, boolean isVolumeController) {
         AudioSystem.setDynamicPolicyCallback(mDynPolicyCallback);
 
         if (DEBUG_AP) Log.d(TAG, "registerAudioPolicy for " + pcb.asBinder()
@@ -6987,7 +6999,7 @@
                     return null;
                 }
                 AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, pcb, hasFocusListener,
-                        isFocusPolicy);
+                        isFocusPolicy, isVolumeController);
                 pcb.asBinder().linkToDeath(app, 0/*flags*/);
                 regId = app.getRegistrationId();
                 mAudioPolicies.put(pcb.asBinder(), app);
@@ -7053,6 +7065,23 @@
         return AudioManager.SUCCESS;
     }
 
+    private final Object mExtVolumeControllerLock = new Object();
+    private IAudioPolicyCallback mExtVolumeController;
+    private void setExtVolumeController(IAudioPolicyCallback apc) {
+        if (!mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_handleVolumeKeysInWindowManager)) {
+            Log.e(TAG, "Cannot set external volume controller: device not set for volume keys" +
+                    " handled in PhoneWindowManager");
+            return;
+        }
+        synchronized (mExtVolumeControllerLock) {
+            if (mExtVolumeController != null && !mExtVolumeController.asBinder().pingBinder()) {
+                Log.e(TAG, "Cannot set external volume controller: existing controller");
+            }
+            mExtVolumeController = apc;
+        }
+    }
+
     private void dumpAudioPolicies(PrintWriter pw) {
         pw.println("\nAudio policies:");
         synchronized (mAudioPolicies) {
@@ -7185,8 +7214,9 @@
      */
     public class AudioPolicyProxy extends AudioPolicyConfig implements IBinder.DeathRecipient {
         private static final String TAG = "AudioPolicyProxy";
-        IAudioPolicyCallback mPolicyCallback;
-        boolean mHasFocusListener;
+        final IAudioPolicyCallback mPolicyCallback;
+        final boolean mHasFocusListener;
+        final boolean mIsVolumeController;
         /**
          * Audio focus ducking behavior for an audio policy.
          * This variable reflects the value that was successfully set in
@@ -7198,11 +7228,12 @@
         boolean mIsFocusPolicy = false;
 
         AudioPolicyProxy(AudioPolicyConfig config, IAudioPolicyCallback token,
-                boolean hasFocusListener, boolean isFocusPolicy) {
+                boolean hasFocusListener, boolean isFocusPolicy, boolean isVolumeController) {
             super(config);
             setRegistration(new String(config.hashCode() + ":ap:" + mAudioPolicyCounter++));
             mPolicyCallback = token;
             mHasFocusListener = hasFocusListener;
+            mIsVolumeController = isVolumeController;
             if (mHasFocusListener) {
                 mMediaFocusControl.addFocusFollower(mPolicyCallback);
                 // can only ever be true if there is a focus listener
@@ -7211,6 +7242,9 @@
                     mMediaFocusControl.setFocusPolicy(mPolicyCallback);
                 }
             }
+            if (mIsVolumeController) {
+                setExtVolumeController(mPolicyCallback);
+            }
             connectMixes();
         }
 
@@ -7220,6 +7254,11 @@
                 release();
                 mAudioPolicies.remove(mPolicyCallback.asBinder());
             }
+            if (mIsVolumeController) {
+                synchronized (mExtVolumeControllerLock) {
+                    mExtVolumeController = null;
+                }
+            }
         }
 
         String getRegistrationId() {
diff --git a/services/core/java/com/android/server/pm/PackageSignatures.java b/services/core/java/com/android/server/pm/PackageSignatures.java
index bfc858f..0229a37 100644
--- a/services/core/java/com/android/server/pm/PackageSignatures.java
+++ b/services/core/java/com/android/server/pm/PackageSignatures.java
@@ -296,6 +296,7 @@
                 PackageManagerService.reportSettingsProblem(Log.WARN,
                         "Unknown element under <sigs>: "
                                 + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
             }
         }
         return pos;
diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java
index a538967..847c90a 100644
--- a/services/core/java/com/android/server/power/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/BatterySaverPolicy.java
@@ -33,6 +33,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.power.batterysaver.BatterySavingStats;
 import com.android.server.power.batterysaver.CpuFrequencies;
 
 import java.io.PrintWriter;
@@ -498,6 +499,8 @@
             pw.print("  Noninteractive File values:\n");
             dumpMap(pw, "    ", mFilesForNoninteractive);
             pw.println();
+            pw.println();
+            BatterySavingStats.getInstance().dump(pw, "  ");
         }
     }
 
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
index d4627c2..32f38b7 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
@@ -28,6 +28,7 @@
 import android.content.IntentFilter;
 import android.hardware.power.V1_0.PowerHint;
 import android.net.Uri;
+import android.os.BatteryManager;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -50,6 +51,9 @@
 import com.android.server.power.BatterySaverPolicy;
 import com.android.server.power.BatterySaverPolicy.BatterySaverPolicyListener;
 import com.android.server.power.PowerManagerService;
+import com.android.server.power.batterysaver.BatterySavingStats.BatterySaverState;
+import com.android.server.power.batterysaver.BatterySavingStats.DozeState;
+import com.android.server.power.batterysaver.BatterySavingStats.InteractiveState;
 
 import java.util.ArrayList;
 
@@ -70,6 +74,8 @@
 
     private final BatterySaverPolicy mBatterySaverPolicy;
 
+    private final BatterySavingStats mBatterySavingStats;
+
     private static final String WARNING_LINK_URL = "http://goto.google.com/extreme-battery-saver";
 
     @GuardedBy("mLock")
@@ -78,6 +84,9 @@
     @GuardedBy("mLock")
     private boolean mEnabled;
 
+    @GuardedBy("mLock")
+    private boolean mIsPluggedIn;
+
     /**
      * Previously enabled or not; only for the event logging. Only use it from
      * {@link #handleBatterySaverStateChanged}.
@@ -104,15 +113,28 @@
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
+            if (DEBUG) {
+                Slog.d(TAG, "onReceive: " + intent);
+            }
             switch (intent.getAction()) {
                 case Intent.ACTION_SCREEN_ON:
                 case Intent.ACTION_SCREEN_OFF:
                     if (!isEnabled()) {
+                        updateBatterySavingStats();
                         return; // No need to send it if not enabled.
                     }
                     // Don't send the broadcast, because we never did so in this case.
                     mHandler.postStateChanged(/*sendBroadcast=*/ false);
                     break;
+                case Intent.ACTION_BATTERY_CHANGED:
+                    synchronized (mLock) {
+                        mIsPluggedIn = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0);
+                    }
+                    // Fall-through.
+                case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
+                case PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED:
+                    updateBatterySavingStats();
+                    break;
             }
         }
     };
@@ -126,6 +148,7 @@
         mBatterySaverPolicy = policy;
         mBatterySaverPolicy.addListener(this);
         mFileUpdater = new FileUpdater(context);
+        mBatterySavingStats = BatterySavingStats.getInstance();
 
         // Initialize plugins.
         final ArrayList<Plugin> plugins = new ArrayList<>();
@@ -149,6 +172,9 @@
     public void systemReady() {
         final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
         filter.addAction(Intent.ACTION_SCREEN_OFF);
+        filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+        filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+        filter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
         mContext.registerReceiver(mReceiver, filter);
 
         mFileUpdater.systemReady(LocalServices.getService(ActivityManagerInternal.class)
@@ -280,7 +306,6 @@
             enabled = mEnabled;
             mIsInteractive = isInteractive;
 
-
             if (enabled) {
                 fileValues = mBatterySaverPolicy.getFileValues(isInteractive);
             } else {
@@ -293,6 +318,8 @@
             pmi.powerHint(PowerHint.LOW_POWER, enabled ? 1 : 0);
         }
 
+        updateBatterySavingStats();
+
         if (ArrayUtils.isEmpty(fileValues)) {
             mFileUpdater.restoreDefault();
         } else {
@@ -332,7 +359,6 @@
             mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
                     Manifest.permission.DEVICE_POWER);
 
-
             for (LowPowerModeListener listener : listeners) {
                 final PowerSaveState result =
                         mBatterySaverPolicy.getBatterySaverPolicy(
@@ -388,4 +414,28 @@
                     foregroundUser);
         }
     }
+
+    private void updateBatterySavingStats() {
+        final PowerManager pm = getPowerManager();
+        if (pm == null) {
+            Slog.wtf(TAG, "PowerManager not initialized");
+            return;
+        }
+        final boolean isInteractive = pm.isInteractive();
+        final int dozeMode =
+                pm.isDeviceIdleMode() ? DozeState.DEEP
+                        : pm.isLightDeviceIdleMode() ? DozeState.LIGHT
+                        : DozeState.NOT_DOZING;
+
+        synchronized (mLock) {
+            if (mIsPluggedIn) {
+                mBatterySavingStats.startCharging();
+                return;
+            }
+            mBatterySavingStats.transitionState(
+                    mEnabled ? BatterySaverState.ON : BatterySaverState.OFF,
+                    isInteractive ? InteractiveState.INTERACTIVE : InteractiveState.NON_INTERACTIVE,
+                    dozeMode);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java b/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java
new file mode 100644
index 0000000..df4f8ec
--- /dev/null
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2018 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.power.batterysaver;
+
+import android.os.BatteryManagerInternal;
+import android.os.SystemClock;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.EventLogTags;
+import com.android.server.LocalServices;
+import com.android.server.power.BatterySaverPolicy;
+
+import java.io.PrintWriter;
+
+/**
+ * This class keeps track of battery drain rate.
+ *
+ * Test:
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySavingStatsTest.java
+ */
+public class BatterySavingStats {
+
+    private static final String TAG = "BatterySavingStats";
+
+    private static final boolean DEBUG = BatterySaverPolicy.DEBUG;
+
+    private final Object mLock = new Object();
+
+    /** Whether battery saver is on or off. */
+    interface BatterySaverState {
+        int OFF = 0;
+        int ON = 1;
+
+        int SHIFT = 0;
+        int BITS = 1;
+        int MASK = (1 << BITS) - 1;
+
+        static int fromIndex(int index) {
+            return (index >> SHIFT) & MASK;
+        }
+    }
+
+    /** Whether the device is interactive (i.e. screen on) or not. */
+    interface InteractiveState {
+        int NON_INTERACTIVE = 0;
+        int INTERACTIVE = 1;
+
+        int SHIFT = BatterySaverState.SHIFT + BatterySaverState.BITS;
+        int BITS = 1;
+        int MASK = (1 << BITS) - 1;
+
+        static int fromIndex(int index) {
+            return (index >> SHIFT) & MASK;
+        }
+    }
+
+    /** Doze mode. */
+    interface DozeState {
+        int NOT_DOZING = 0;
+        int LIGHT = 1;
+        int DEEP = 2;
+
+        int SHIFT = InteractiveState.SHIFT + InteractiveState.BITS;
+        int BITS = 2;
+        int MASK = (1 << BITS) - 1;
+
+        static int fromIndex(int index) {
+            return (index >> SHIFT) & MASK;
+        }
+    }
+
+    /**
+     * Various stats in each state.
+     */
+    static class Stat {
+        public long startTime;
+        public long endTime;
+
+        public int startBatteryLevel;
+        public int endBatteryLevel;
+
+        public long totalTimeMillis;
+        public int totalBatteryDrain;
+
+        public long totalMinutes() {
+            return totalTimeMillis / 60_000;
+        }
+
+        public double drainPerHour() {
+            if (totalTimeMillis == 0) {
+                return 0;
+            }
+            return (double) totalBatteryDrain / (totalTimeMillis / (60.0 * 60 * 1000));
+        }
+
+        @VisibleForTesting
+        String toStringForTest() {
+            return "{" + totalMinutes() + "m," + totalBatteryDrain + ","
+                    + String.format("%.2f", drainPerHour()) + "}";
+        }
+    }
+
+    private static BatterySavingStats sInstance;
+
+    private BatteryManagerInternal mBatteryManagerInternal;
+
+    private static final int STATE_NOT_INITIALIZED = -1;
+    private static final int STATE_CHARGING = -2;
+
+    /**
+     * Current state, one of STATE_* or values returned by {@link #statesToIndex}.
+     */
+    @GuardedBy("mLock")
+    private int mCurrentState = STATE_NOT_INITIALIZED;
+
+    /**
+     * Stats in each state.
+     */
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    final ArrayMap<Integer, Stat> mStats = new ArrayMap<>();
+
+    /**
+     * Don't call it directly -- use {@link #getInstance()}. Not private for testing.
+     */
+    @VisibleForTesting
+    BatterySavingStats() {
+        mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class);
+    }
+
+    public static synchronized BatterySavingStats getInstance() {
+        if (sInstance == null) {
+            sInstance = new BatterySavingStats();
+        }
+        return sInstance;
+    }
+
+    private BatteryManagerInternal getBatteryManagerInternal() {
+        if (mBatteryManagerInternal == null) {
+            mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class);
+        }
+        return mBatteryManagerInternal;
+    }
+
+    /**
+     * Takes a state triplet and generates a state index.
+     */
+    @VisibleForTesting
+    static int statesToIndex(
+            int batterySaverState, int interactiveState, int dozeState) {
+        int ret = batterySaverState & BatterySaverState.MASK;
+        ret |= (interactiveState & InteractiveState.MASK) << InteractiveState.SHIFT;
+        ret |= (dozeState & DozeState.MASK) << DozeState.SHIFT;
+        return ret;
+    }
+
+    /**
+     * Takes a state index and returns a string for logging.
+     */
+    @VisibleForTesting
+    static String stateToString(int state) {
+        switch (state) {
+            case STATE_NOT_INITIALIZED:
+                return "NotInitialized";
+            case STATE_CHARGING:
+                return "Charging";
+        }
+        return "BS=" + BatterySaverState.fromIndex(state)
+                + ",I=" + InteractiveState.fromIndex(state)
+                + ",D=" + DozeState.fromIndex(state);
+    }
+
+    /**
+     * @return {@link Stat} fo a given state.
+     */
+    @VisibleForTesting
+    Stat getStat(int stateIndex) {
+        synchronized (mLock) {
+            Stat stat = mStats.get(stateIndex);
+            if (stat == null) {
+                stat = new Stat();
+                mStats.put(stateIndex, stat);
+            }
+            return stat;
+        }
+    }
+
+    /**
+     * @return {@link Stat} fo a given state triplet.
+     */
+    private Stat getStat(int batterySaverState, int interactiveState, int dozeState) {
+        return getStat(statesToIndex(batterySaverState, interactiveState, dozeState));
+    }
+
+    long injectCurrentTime() {
+        return SystemClock.elapsedRealtime();
+    }
+
+    int injectBatteryLevel() {
+        final BatteryManagerInternal bmi = getBatteryManagerInternal();
+        if (bmi == null) {
+            Slog.wtf(TAG, "BatteryManagerInternal not initialized");
+            return 0;
+        }
+        return bmi.getBatteryChargeCounter();
+    }
+
+    /**
+     * Called from the outside whenever any of the states changes, when the device is not plugged
+     * in.
+     */
+    public void transitionState(int batterySaverState, int interactiveState, int dozeState) {
+        synchronized (mLock) {
+
+            final int newState = statesToIndex(
+                    batterySaverState, interactiveState, dozeState);
+            if (mCurrentState == newState) {
+                return;
+            }
+
+            endLastStateLocked();
+            startNewStateLocked(newState);
+        }
+    }
+
+    /**
+     * Called from the outside when the device is plugged in.
+     */
+    public void startCharging() {
+        synchronized (mLock) {
+            if (mCurrentState < 0) {
+                return;
+            }
+
+            endLastStateLocked();
+            startNewStateLocked(STATE_CHARGING);
+        }
+    }
+
+    private void endLastStateLocked() {
+        if (mCurrentState < 0) {
+            return;
+        }
+        final Stat stat = getStat(mCurrentState);
+
+        stat.endBatteryLevel = injectBatteryLevel();
+        stat.endTime = injectCurrentTime();
+
+        final long deltaTime = stat.endTime - stat.startTime;
+        final int deltaDrain = stat.startBatteryLevel - stat.endBatteryLevel;
+
+        stat.totalTimeMillis += deltaTime;
+        stat.totalBatteryDrain += deltaDrain;
+
+        if (DEBUG) {
+            Slog.d(TAG, "State summary: " + stateToString(mCurrentState)
+                    + ": " + (deltaTime / 1_000) + "s "
+                    + "Start level: " + stat.startBatteryLevel + "uA "
+                    + "End level: " + stat.endBatteryLevel + "uA "
+                    + deltaDrain + "uA");
+        }
+        EventLogTags.writeBatterySavingStats(
+                BatterySaverState.fromIndex(mCurrentState),
+                InteractiveState.fromIndex(mCurrentState),
+                DozeState.fromIndex(mCurrentState),
+                deltaTime,
+                deltaDrain,
+                stat.totalTimeMillis,
+                stat.totalBatteryDrain);
+    }
+
+    private void startNewStateLocked(int newState) {
+        if (DEBUG) {
+            Slog.d(TAG, "New state: " + stateToString(newState));
+        }
+        mCurrentState = newState;
+
+        if (mCurrentState < 0) {
+            return;
+        }
+
+        final Stat stat = getStat(mCurrentState);
+        stat.startBatteryLevel = injectBatteryLevel();
+        stat.startTime = injectCurrentTime();
+        stat.endTime = 0;
+    }
+
+    public void dump(PrintWriter pw, String indent) {
+        synchronized (mLock) {
+            pw.print(indent);
+            pw.println("Battery Saving Stats:");
+
+            indent = indent + "  ";
+
+            pw.print(indent);
+            pw.println("Battery Saver:       Off                                 On");
+            dumpLineLocked(pw, indent, InteractiveState.NON_INTERACTIVE, "NonIntr",
+                    DozeState.NOT_DOZING, "NonDoze");
+            dumpLineLocked(pw, indent, InteractiveState.INTERACTIVE, "   Intr",
+                    DozeState.NOT_DOZING, "       ");
+
+            dumpLineLocked(pw, indent, InteractiveState.NON_INTERACTIVE, "NonIntr",
+                    DozeState.DEEP, "Deep   ");
+            dumpLineLocked(pw, indent, InteractiveState.INTERACTIVE, "   Intr",
+                    DozeState.DEEP, "       ");
+
+            dumpLineLocked(pw, indent, InteractiveState.NON_INTERACTIVE, "NonIntr",
+                    DozeState.LIGHT, "Light  ");
+            dumpLineLocked(pw, indent, InteractiveState.INTERACTIVE, "   Intr",
+                    DozeState.LIGHT, "       ");
+
+            pw.println();
+        }
+    }
+
+    private void dumpLineLocked(PrintWriter pw, String indent,
+            int interactiveState, String interactiveLabel,
+            int dozeState, String dozeLabel) {
+        pw.print(indent);
+        pw.print(dozeLabel);
+        pw.print(" ");
+        pw.print(interactiveLabel);
+        pw.print(": ");
+
+        final Stat offStat = getStat(BatterySaverState.OFF, interactiveState, dozeState);
+        final Stat onStat = getStat(BatterySaverState.ON, interactiveState, dozeState);
+
+        pw.println(String.format("%6dm %6dmA %8.1fmA/h      %6dm %6dmA %8.1fmA/h",
+                offStat.totalMinutes(),
+                offStat.totalBatteryDrain / 1000,
+                offStat.drainPerHour() / 1000.0,
+                onStat.totalMinutes(),
+                onStat.totalBatteryDrain / 1000,
+                onStat.drainPerHour() / 1000.0));
+    }
+}
+
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 9a51eef..dae7605 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -744,7 +744,8 @@
                 if (deviceOwner != null) {
                     Bundle extras = new Bundle();
                     extras.putParcelable(Intent.EXTRA_USER, UserHandle.of(userHandle));
-                    sendAdminCommandLocked(deviceOwner, action, extras, null);
+                    sendAdminCommandLocked(deviceOwner, action, extras, /* result */ null,
+                            /* inForeground */ true);
                 }
             }
         }
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySavingStatsTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySavingStatsTest.java
new file mode 100644
index 0000000..f788cac
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySavingStatsTest.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2018 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.power.batterysaver;
+
+import static org.junit.Assert.assertEquals;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.power.batterysaver.BatterySavingStats.BatterySaverState;
+import com.android.server.power.batterysaver.BatterySavingStats.DozeState;
+import com.android.server.power.batterysaver.BatterySavingStats.InteractiveState;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+
+/**
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySavingStatsTest.java
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BatterySavingStatsTest {
+    private class BatterySavingStatsTestable extends BatterySavingStats {
+        private long mTime = 1_000_000; // Some random starting time.
+
+        private int mBatteryLevel = 100;
+
+        @Override
+        long injectCurrentTime() {
+            return mTime;
+        }
+
+        @Override
+        int injectBatteryLevel() {
+            return mBatteryLevel;
+        }
+
+        void assertDumpable() {
+            final ByteArrayOutputStream out = new ByteArrayOutputStream();
+            dump(new PrintWriter(out), ""); // Just make sure it won't crash.
+        }
+
+        void advanceClock(int minutes) {
+            mTime += 60_000 * minutes;
+        }
+
+        void drainBattery(int percent) {
+            mBatteryLevel -= percent;
+            if (mBatteryLevel < 0) {
+                mBatteryLevel = 0;
+            }
+        }
+
+        String toDebugString() {
+            final StringBuilder sb = new StringBuilder();
+            String sep = "";
+            for (int i = 0; i < mStats.size(); i++) {
+                sb.append(sep);
+                sb.append(stateToString(mStats.keyAt(i)));
+                sb.append(":");
+                sb.append(mStats.valueAt(i).toStringForTest());
+                sep = "\n";
+            }
+            return sb.toString();
+        }
+    }
+
+    @Test
+    public void testAll() {
+        final BatterySavingStatsTestable target = new BatterySavingStatsTestable();
+
+        target.assertDumpable();
+
+        target.advanceClock(1);
+        target.drainBattery(2);
+
+        target.transitionState(
+                BatterySaverState.OFF,
+                InteractiveState.INTERACTIVE,
+                DozeState.NOT_DOZING);
+
+        target.advanceClock(4);
+        target.drainBattery(1);
+
+        target.transitionState(
+                BatterySaverState.OFF,
+                InteractiveState.NON_INTERACTIVE,
+                DozeState.NOT_DOZING);
+
+        target.advanceClock(2);
+        target.drainBattery(5);
+
+        target.transitionState(
+                BatterySaverState.OFF,
+                InteractiveState.INTERACTIVE,
+                DozeState.NOT_DOZING);
+
+        target.advanceClock(4);
+        target.drainBattery(1);
+
+        target.transitionState(
+                BatterySaverState.OFF,
+                InteractiveState.NON_INTERACTIVE,
+                DozeState.NOT_DOZING);
+
+        target.advanceClock(2);
+        target.drainBattery(5);
+
+        target.transitionState(
+                BatterySaverState.OFF,
+                InteractiveState.INTERACTIVE,
+                DozeState.NOT_DOZING);
+
+        target.advanceClock(3);
+        target.drainBattery(1);
+
+        target.transitionState(
+                BatterySaverState.OFF,
+                InteractiveState.NON_INTERACTIVE,
+                DozeState.LIGHT);
+
+        target.advanceClock(5);
+        target.drainBattery(1);
+
+        target.transitionState(
+                BatterySaverState.OFF,
+                InteractiveState.NON_INTERACTIVE,
+                DozeState.DEEP);
+
+        target.advanceClock(1);
+        target.drainBattery(2);
+
+        target.transitionState(
+                BatterySaverState.ON,
+                InteractiveState.INTERACTIVE,
+                DozeState.NOT_DOZING);
+
+        target.advanceClock(1);
+        target.drainBattery(3);
+
+        target.transitionState(
+                BatterySaverState.OFF,
+                InteractiveState.INTERACTIVE,
+                DozeState.NOT_DOZING);
+
+        target.advanceClock(3);
+        target.drainBattery(5);
+
+        target.transitionState(
+                BatterySaverState.ON,
+                InteractiveState.INTERACTIVE,
+                DozeState.NOT_DOZING);
+
+        target.advanceClock(3);
+        target.drainBattery(5);
+
+        target.startCharging();
+
+        target.advanceClock(5);
+        target.drainBattery(10);
+
+        target.transitionState(
+                BatterySaverState.ON,
+                InteractiveState.INTERACTIVE,
+                DozeState.NOT_DOZING);
+
+        target.advanceClock(5);
+        target.drainBattery(1);
+
+        target.startCharging();
+
+        target.assertDumpable();
+
+        assertEquals(
+                "BS=0,I=0,D=0:{4m,10,150.00}\n" +
+                "BS=1,I=0,D=0:{0m,0,0.00}\n" +
+                "BS=0,I=1,D=0:{14m,8,34.29}\n" +
+                "BS=1,I=1,D=0:{9m,9,60.00}\n" +
+                "BS=0,I=0,D=1:{5m,1,12.00}\n" +
+                "BS=1,I=0,D=1:{0m,0,0.00}\n" +
+                "BS=0,I=1,D=1:{0m,0,0.00}\n" +
+                "BS=1,I=1,D=1:{0m,0,0.00}\n" +
+                "BS=0,I=0,D=2:{1m,2,120.00}\n" +
+                "BS=1,I=0,D=2:{0m,0,0.00}\n" +
+                "BS=0,I=1,D=2:{0m,0,0.00}\n" +
+                "BS=1,I=1,D=2:{0m,0,0.00}",
+                target.toDebugString());
+    }
+}