Merge "Remove java.nio.channels.* classes without test coverage." into nyc-dev
diff --git a/api/system-current.txt b/api/system-current.txt
index 55ed0ab..8ea1854 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -26682,6 +26682,7 @@
     method public boolean reconnect();
     method public boolean removeNetwork(int);
     method public boolean saveConfiguration();
+    method public boolean setMetered(int, boolean);
     method public void setTdlsEnabled(java.net.InetAddress, boolean);
     method public void setTdlsEnabledWithMacAddress(java.lang.String, boolean);
     method public boolean setWifiApConfiguration(android.net.wifi.WifiConfiguration);
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 3385a17..980329f 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -807,8 +807,6 @@
     }
 
     /**
-     * AnimatorSet is only reversible when the set contains no sequential animation, and no child
-     * animators have a start delay.
      * @hide
      */
     @Override
diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java
index 82f69ef..e9d12f5 100644
--- a/core/java/android/text/Html.java
+++ b/core/java/android/text/Html.java
@@ -356,24 +356,48 @@
         }
     }
 
-    private static String getTextStyles(Spanned text, int start, int end) {
-        final StringBuilder style = new StringBuilder(" style=\"margin-top:0; margin-bottom:0;");
+    private static String getTextStyles(Spanned text, int start, int end,
+            boolean forceNoVerticalMargin, boolean includeTextAlign) {
+        String margin = null;
+        String textAlign = null;
 
-        final AlignmentSpan[] alignmentSpans = text.getSpans(start, end, AlignmentSpan.class);
-        final int len = alignmentSpans.length;
-        if (len > 0) {
-            final Layout.Alignment alignment = alignmentSpans[len - 1].getAlignment();
-            if (alignment == Layout.Alignment.ALIGN_NORMAL) {
-                style.append(" text-align:start;");
-            } else if (alignment == Layout.Alignment.ALIGN_CENTER) {
-                style.append(" text-align:center;");
-            } else if (alignment == Layout.Alignment.ALIGN_OPPOSITE) {
-                style.append(" text-align:end;");
+        if (forceNoVerticalMargin) {
+            margin = "margin-top:0; margin-bottom:0;";
+        }
+        if (includeTextAlign) {
+            final AlignmentSpan[] alignmentSpans = text.getSpans(start, end, AlignmentSpan.class);
+
+            // Only use the last AlignmentSpan with flag SPAN_PARAGRAPH
+            for (int i = alignmentSpans.length - 1; i >= 0; i--) {
+                AlignmentSpan s = alignmentSpans[i];
+                if ((text.getSpanFlags(s) & Spanned.SPAN_PARAGRAPH) == Spanned.SPAN_PARAGRAPH) {
+                    final Layout.Alignment alignment = s.getAlignment();
+                    if (alignment == Layout.Alignment.ALIGN_NORMAL) {
+                        textAlign = "text-align:start;";
+                    } else if (alignment == Layout.Alignment.ALIGN_CENTER) {
+                        textAlign = "text-align:center;";
+                    } else if (alignment == Layout.Alignment.ALIGN_OPPOSITE) {
+                        textAlign = "text-align:end;";
+                    }
+                    break;
+                }
             }
         }
 
-        style.append("\"");
-        return style.toString();
+        if (margin == null && textAlign == null) {
+            return "";
+        }
+
+        final StringBuilder style = new StringBuilder(" style=\"");
+        if (margin != null && textAlign != null) {
+            style.append(margin).append(" ").append(textAlign);
+        } else if (margin != null) {
+            style.append(margin);
+        } else if (textAlign != null) {
+            style.append(textAlign);
+        }
+
+        return style.append("\"").toString();
     }
 
     private static void withinBlockquote(StringBuilder out, Spanned text, int start, int end,
@@ -395,46 +419,55 @@
                 next = end;
             }
 
-            boolean isListItem = false;
-            ParagraphStyle[] paragraphStyles = text.getSpans(i, next, ParagraphStyle.class);
-            for (ParagraphStyle paragraphStyle : paragraphStyles) {
-                final int spanFlags = text.getSpanFlags(paragraphStyle);
-                if ((spanFlags & Spanned.SPAN_PARAGRAPH) == Spanned.SPAN_PARAGRAPH
-                        && paragraphStyle instanceof BulletSpan) {
-                    isListItem = true;
-                    break;
+            if (next == i) {
+                if (isInList) {
+                    // Current paragraph is no longer a list item; close the previously opened list
+                    isInList = false;
+                    out.append("</ul>\n");
                 }
-            }
-
-            if (isListItem && !isInList) {
-                // Current paragraph is the first item in a list
-                isInList = true;
-                out.append("<ul>\n");
-            }
-
-            if (isInList && !isListItem) {
-                // Current paragraph is no longer a list item; close the previously opened list
-                isInList = false;
-                out.append("</ul>\n");
-            }
-
-            String tagType = isListItem ? "li" : "p";
-            out.append("<").append(tagType).append(getTextDirection(text, start, next))
-                    .append(getTextStyles(text, start, next)).append(">");
-
-            if (next - i == 0) {
-                out.append("<br>");
+                out.append("<br>\n");
             } else {
+                boolean isListItem = false;
+                ParagraphStyle[] paragraphStyles = text.getSpans(i, next, ParagraphStyle.class);
+                for (ParagraphStyle paragraphStyle : paragraphStyles) {
+                    final int spanFlags = text.getSpanFlags(paragraphStyle);
+                    if ((spanFlags & Spanned.SPAN_PARAGRAPH) == Spanned.SPAN_PARAGRAPH
+                            && paragraphStyle instanceof BulletSpan) {
+                        isListItem = true;
+                        break;
+                    }
+                }
+
+                if (isListItem && !isInList) {
+                    // Current paragraph is the first item in a list
+                    isInList = true;
+                    out.append("<ul")
+                            .append(getTextStyles(text, i, next, true, false))
+                            .append(">\n");
+                }
+
+                if (isInList && !isListItem) {
+                    // Current paragraph is no longer a list item; close the previously opened list
+                    isInList = false;
+                    out.append("</ul>\n");
+                }
+
+                String tagType = isListItem ? "li" : "p";
+                out.append("<").append(tagType)
+                        .append(getTextDirection(text, i, next))
+                        .append(getTextStyles(text, i, next, !isListItem, true))
+                        .append(">");
+
                 withinParagraph(out, text, i, next);
-            }
 
-            out.append("</");
-            out.append(tagType);
-            out.append(">\n");
+                out.append("</");
+                out.append(tagType);
+                out.append(">\n");
 
-            if (next == end && isInList) {
-                isInList = false;
-                out.append("</ul>\n");
+                if (next == end && isInList) {
+                    isInList = false;
+                    out.append("</ul>\n");
+                }
             }
 
             next++;
@@ -654,6 +687,9 @@
     private int mFlags;
 
     private static Pattern sTextAlignPattern;
+    private static Pattern sForegroundColorPattern;
+    private static Pattern sBackgroundColorPattern;
+    private static Pattern sTextDecorationPattern;
 
     private static Pattern getTextAlignPattern() {
         if (sTextAlignPattern == null) {
@@ -662,6 +698,30 @@
         return sTextAlignPattern;
     }
 
+    private static Pattern getForegroundColorPattern() {
+        if (sForegroundColorPattern == null) {
+            sForegroundColorPattern = Pattern.compile(
+                    "(?:\\s+|\\A)color\\s*:\\s*(\\S*)\\b");
+        }
+        return sForegroundColorPattern;
+    }
+
+    private static Pattern getBackgroundColorPattern() {
+        if (sBackgroundColorPattern == null) {
+            sBackgroundColorPattern = Pattern.compile(
+                    "(?:\\s+|\\A)background(?:-color)?\\s*:\\s*(\\S*)\\b");
+        }
+        return sBackgroundColorPattern;
+    }
+
+    private static Pattern getTextDecorationPattern() {
+        if (sTextDecorationPattern == null) {
+            sTextDecorationPattern = Pattern.compile(
+                    "(?:\\s+|\\A)text-decoration\\s*:\\s*(\\S*)\\b");
+        }
+        return sTextDecorationPattern;
+    }
+
     public HtmlToSpannedConverter( String source, Html.ImageGetter imageGetter,
             Html.TagHandler tagHandler, Parser parser, int flags) {
         mSource = source;
@@ -715,8 +775,15 @@
             // so we can safely emit the linebreaks when we handle the close tag.
         } else if (tag.equalsIgnoreCase("p")) {
             startBlockElement(mSpannableStringBuilder, attributes, getMarginParagraph());
+            startCssStyle(mSpannableStringBuilder, attributes);
+        } else if (tag.equalsIgnoreCase("ul")) {
+            startBlockElement(mSpannableStringBuilder, attributes, getMarginList());
+        } else if (tag.equalsIgnoreCase("li")) {
+            startLi(mSpannableStringBuilder, attributes);
         } else if (tag.equalsIgnoreCase("div")) {
             startBlockElement(mSpannableStringBuilder, attributes, getMarginDiv());
+        } else if (tag.equalsIgnoreCase("span")) {
+            startCssStyle(mSpannableStringBuilder, attributes);
         } else if (tag.equalsIgnoreCase("strong")) {
             start(mSpannableStringBuilder, new Bold());
         } else if (tag.equalsIgnoreCase("b")) {
@@ -768,9 +835,16 @@
         if (tag.equalsIgnoreCase("br")) {
             handleBr(mSpannableStringBuilder);
         } else if (tag.equalsIgnoreCase("p")) {
+            endCssStyle(mSpannableStringBuilder);
             endBlockElement(mSpannableStringBuilder);
+        } else if (tag.equalsIgnoreCase("ul")) {
+            endBlockElement(mSpannableStringBuilder);
+        } else if (tag.equalsIgnoreCase("li")) {
+            endLi(mSpannableStringBuilder);
         } else if (tag.equalsIgnoreCase("div")) {
             endBlockElement(mSpannableStringBuilder);
+        } else if (tag.equalsIgnoreCase("span")) {
+            endCssStyle(mSpannableStringBuilder);
         } else if (tag.equalsIgnoreCase("strong")) {
             end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD));
         } else if (tag.equalsIgnoreCase("b")) {
@@ -824,6 +898,14 @@
         return getMargin(Html.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING);
     }
 
+    private int getMarginListItem() {
+        return getMargin(Html.FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM);
+    }
+
+    private int getMarginList() {
+        return getMargin(Html.FROM_HTML_SEPARATOR_LINE_BREAK_LIST);
+    }
+
     private int getMarginDiv() {
         return getMargin(Html.FROM_HTML_SEPARATOR_LINE_BREAK_DIV);
     }
@@ -905,6 +987,18 @@
         text.append('\n');
     }
 
+    private void startLi(Editable text, Attributes attributes) {
+        startBlockElement(text, attributes, getMarginListItem());
+        start(text, new Bullet());
+        startCssStyle(text, attributes);
+    }
+
+    private static void endLi(Editable text) {
+        endCssStyle(text);
+        endBlockElement(text);
+        end(text, Bullet.class, new BulletSpan());
+    }
+
     private void startBlockquote(Editable text, Attributes attributes) {
         startBlockElement(text, attributes, getMarginBlockquote());
         start(text, new Blockquote());
@@ -970,6 +1064,55 @@
         }
     }
 
+    private static void startCssStyle(Editable text, Attributes attributes) {
+        String style = attributes.getValue("", "style");
+        if (style != null) {
+            final int len = text.length();
+            Matcher m = getForegroundColorPattern().matcher(style);
+            if (m.find()) {
+                int c = Color.getHtmlColor(m.group(1));
+                if (c != -1) {
+                    text.setSpan(new Foreground(c | 0xFF000000), len, len,
+                            Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+                }
+            }
+
+            m = getBackgroundColorPattern().matcher(style);
+            if (m.find()) {
+                int c = Color.getHtmlColor(m.group(1));
+                if (c != -1) {
+                    text.setSpan(new Background(c | 0xFF000000), len, len,
+                            Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+                }
+            }
+
+            m = getTextDecorationPattern().matcher(style);
+            if (m.find()) {
+                String textDecoration = m.group(1);
+                if (textDecoration.equalsIgnoreCase("line-through")) {
+                    text.setSpan(new Strikethrough(), len, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+                }
+            }
+        }
+    }
+
+    private static void endCssStyle(Editable text) {
+        Strikethrough s = getLast(text, Strikethrough.class);
+        if (s != null) {
+            setSpanFromMark(text, s, new StrikethroughSpan());
+        }
+
+        Background b = getLast(text, Background.class);
+        if (b != null) {
+            setSpanFromMark(text, b, new BackgroundColorSpan(b.mBackgroundColor));
+        }
+
+        Foreground f = getLast(text, Foreground.class);
+        if (f != null) {
+            setSpanFromMark(text, f, new ForegroundColorSpan(f.mForegroundColor));
+        }
+    }
+
     private static void startImg(Editable text, Attributes attributes, Html.ImageGetter img) {
         String src = attributes.getValue("", "src");
         Drawable d = null;
@@ -1132,6 +1275,7 @@
     private static class Blockquote { }
     private static class Super { }
     private static class Sub { }
+    private static class Bullet { }
 
     private static class Font {
         public String mColor;
@@ -1151,6 +1295,22 @@
         }
     }
 
+    private static class Foreground {
+        private int mForegroundColor;
+
+        public Foreground(int foregroundColor) {
+            mForegroundColor = foregroundColor;
+        }
+    }
+
+    private static class Background {
+        private int mBackgroundColor;
+
+        public Background(int backgroundColor) {
+            mBackgroundColor = backgroundColor;
+        }
+    }
+
     private static class Heading {
         private int mLevel;
 
diff --git a/core/java/com/android/internal/util/LineBreakBufferedWriter.java b/core/java/com/android/internal/util/LineBreakBufferedWriter.java
index f831e7a..552a93f 100644
--- a/core/java/com/android/internal/util/LineBreakBufferedWriter.java
+++ b/core/java/com/android/internal/util/LineBreakBufferedWriter.java
@@ -96,7 +96,7 @@
 
     @Override
     public void write(int c) {
-        if (bufferIndex < bufferSize) {
+        if (bufferIndex < buffer.length) {
             buffer[bufferIndex] = (char)c;
             bufferIndex++;
             if ((char)c == '\n') {
diff --git a/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp b/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp
index 14badb7..7a3c598 100644
--- a/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp
+++ b/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp
@@ -43,13 +43,12 @@
     return env;
 }
 
-static AnimationListener* createAnimationListener(JNIEnv* env, jobject finishListener, jint id) {
+static AnimationListener* createAnimationListener(JNIEnv* env, jobject finishListener) {
     class AnimationListenerBridge : public AnimationListener {
     public:
-        AnimationListenerBridge(JNIEnv* env, jobject finishListener, jint id) {
+        AnimationListenerBridge(JNIEnv* env, jobject finishListener) {
             mFinishListener = env->NewGlobalRef(finishListener);
             env->GetJavaVM(&mJvm);
-            mId = id;
         }
 
         virtual ~AnimationListenerBridge() {
@@ -64,7 +63,7 @@
             env->CallStaticVoidMethod(
                     gVectorDrawableAnimatorClassInfo.clazz,
                     gVectorDrawableAnimatorClassInfo.callOnFinished,
-                    mFinishListener, mId);
+                    mFinishListener);
             releaseJavaObject();
         }
 
@@ -77,9 +76,8 @@
 
         JavaVM* mJvm;
         jobject mFinishListener;
-        jint mId;
     };
-    return new AnimationListenerBridge(env, finishListener, id);
+    return new AnimationListenerBridge(env, finishListener);
 }
 
 static void addAnimator(JNIEnv*, jobject, jlong animatorSetPtr, jlong propertyHolderPtr,
@@ -144,16 +142,15 @@
     holder->setPropertyDataSource(propertyData, length);
     env->ReleaseFloatArrayElements(srcData, propertyData, JNI_ABORT);
 }
-static void start(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener, jint id) {
+static void start(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener) {
     PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr);
-    AnimationListener* listener = createAnimationListener(env, finishListener, id);
+    // TODO: keep a ref count in finish listener
+    AnimationListener* listener = createAnimationListener(env, finishListener);
     set->start(listener);
 }
 
-static void reverse(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener, jint id) {
-    PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr);
-    AnimationListener* listener = createAnimationListener(env, finishListener, id);
-    set->reverse(listener);
+static void reverse(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener) {
+    // TODO: implement reverse
 }
 
 static void end(JNIEnv*, jobject, jlong animatorSetPtr) {
@@ -175,8 +172,8 @@
     {"nCreatePathPropertyHolder", "!(JIFF)J", (void*)createPathPropertyHolder},
     {"nCreateRootAlphaPropertyHolder", "!(JFF)J", (void*)createRootAlphaPropertyHolder},
     {"nSetPropertyHolderData", "(J[FI)V", (void*)setPropertyHolderData},
-    {"nStart", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;I)V", (void*)start},
-    {"nReverse", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;I)V", (void*)reverse},
+    {"nStart", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;)V", (void*)start},
+    {"nReverse", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;)V", (void*)reverse},
     {"nEnd", "!(J)V", (void*)end},
     {"nReset", "!(J)V", (void*)reset},
 };
@@ -189,7 +186,7 @@
 
     gVectorDrawableAnimatorClassInfo.callOnFinished = GetStaticMethodIDOrDie(
             env, gVectorDrawableAnimatorClassInfo.clazz, "callOnFinished",
-            "(Landroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;I)V");
+            "(Landroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;)V");
     return RegisterMethodsOrDie(env, "android/graphics/drawable/AnimatedVectorDrawable",
             gMethods, NELEM(gMethods));
 }
diff --git a/core/jni/android_view_RenderNodeAnimator.cpp b/core/jni/android_view_RenderNodeAnimator.cpp
index c9eac79..0926e9b 100644
--- a/core/jni/android_view_RenderNodeAnimator.cpp
+++ b/core/jni/android_view_RenderNodeAnimator.cpp
@@ -184,7 +184,7 @@
 
 static void end(JNIEnv* env, jobject clazz, jlong animatorPtr) {
     BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr);
-    animator->cancel();
+    animator->end();
 }
 
 // ----------------------------------------------------------------------------
diff --git a/core/res/res/layout/floating_popup_container.xml b/core/res/res/layout/floating_popup_container.xml
index dd161e3..ca03737 100644
--- a/core/res/res/layout/floating_popup_container.xml
+++ b/core/res/res/layout/floating_popup_container.xml
@@ -19,8 +19,8 @@
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:padding="0dp"
-    android:layout_margin="20dp"
-    android:elevation="2dp"
+    android:layout_margin="@android:dimen/text_edit_floating_toolbar_margin"
+    android:elevation="@android:dimen/text_edit_floating_toolbar_elevation"
     android:focusable="true"
     android:focusableInTouchMode="true"
     android:background="?attr/floatingToolbarPopupBackgroundDrawable"/>
diff --git a/core/res/res/layout/text_edit_suggestion_container.xml b/core/res/res/layout/text_edit_suggestion_container.xml
index 17e93d0..b2589da 100644
--- a/core/res/res/layout/text_edit_suggestion_container.xml
+++ b/core/res/res/layout/text_edit_suggestion_container.xml
@@ -22,8 +22,8 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:orientation="vertical"
-        android:elevation="2dp"
-        android:layout_margin="20dp"
+        android:elevation="@android:dimen/text_edit_floating_toolbar_elevation"
+        android:layout_margin="@android:dimen/text_edit_floating_toolbar_margin"
         android:background="@drawable/text_edit_suggestions_window"
         android:dropDownSelector="@drawable/list_selector_background"
         android:divider="@null">
diff --git a/core/res/res/layout/text_edit_suggestion_container_material.xml b/core/res/res/layout/text_edit_suggestion_container_material.xml
index 7826803..20a80489 100644
--- a/core/res/res/layout/text_edit_suggestion_container_material.xml
+++ b/core/res/res/layout/text_edit_suggestion_container_material.xml
@@ -24,8 +24,8 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:background="?android:attr/floatingToolbarPopupBackgroundDrawable"
-        android:elevation="2dp"
-        android:layout_margin="20dp"
+        android:elevation="@android:dimen/text_edit_floating_toolbar_elevation"
+        android:layout_margin="@android:dimen/text_edit_floating_toolbar_margin"
         android:orientation="vertical"
         android:divider="?android:attr/listDivider"
         android:showDividers="middle">
diff --git a/core/res/res/values/dimens_material.xml b/core/res/res/values/dimens_material.xml
index 96a81d1..2fe4f66 100644
--- a/core/res/res/values/dimens_material.xml
+++ b/core/res/res/values/dimens_material.xml
@@ -82,6 +82,9 @@
     <dimen name="text_size_medium_material">18sp</dimen>
     <dimen name="text_size_small_material">14sp</dimen>
 
+    <dimen name="text_edit_floating_toolbar_elevation">2dp</dimen>
+    <dimen name="text_edit_floating_toolbar_margin">20dp</dimen>
+
     <dimen name="floating_window_z">16dp</dimen>
     <dimen name="floating_window_margin_left">16dp</dimen>
     <dimen name="floating_window_margin_top">8dp</dimen>
diff --git a/core/tests/coretests/src/com/android/internal/util/LineBreakBufferedWriterTest.java b/core/tests/coretests/src/com/android/internal/util/LineBreakBufferedWriterTest.java
index 49ae104..4845c4e 100644
--- a/core/tests/coretests/src/com/android/internal/util/LineBreakBufferedWriterTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/LineBreakBufferedWriterTest.java
@@ -180,6 +180,22 @@
         assertOutput("aaaaaaaaaabbbbbc\nd", "ddddddddd");
     }
 
+    public void testMoreThenInitialCapacitySimpleWrites() {
+        // This check is different from testMoreThanBufferSizeChar. The initial capacity is lower
+        // than the maximum buffer size here.
+        final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 1024, 3);
+
+        for(int i = 0; i < 10; i++) {
+            lw.print('$');
+        }
+        for(int i = 0; i < 10; i++) {
+            lw.print('%');
+        }
+        lw.flush();
+
+        assertOutput("$$$$$$$$$$%%%%%%%%%%");
+    }
+
     private void assertOutput(String... golden) {
         List<String> goldList = createTestGolden(golden);
         assertEquals(goldList, mWriter.getStrings());
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index 99bc306..af8ccf5 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -238,6 +238,9 @@
             mAnimatorSet.recordLastSeenTarget((DisplayListCanvas) canvas);
         }
         mAnimatedVectorState.mVectorDrawable.draw(canvas);
+        if (isStarted()) {
+            invalidateSelf();
+        }
     }
 
     @Override
@@ -608,6 +611,10 @@
         return mAnimatorSet.isRunning();
     }
 
+    private boolean isStarted() {
+        return mAnimatorSet.isStarted();
+    }
+
     /**
      * Resets the AnimatedVectorDrawable to the start state as specified in the animators.
      */
@@ -619,6 +626,12 @@
     @Override
     public void start() {
         ensureAnimatorSet();
+
+        // If any one of the animator has not ended, do nothing.
+        if (isStarted()) {
+            return;
+        }
+
         mAnimatorSet.start();
         invalidateSelf();
     }
@@ -639,7 +652,6 @@
     @Override
     public void stop() {
         mAnimatorSet.end();
-        invalidateSelf();
     }
 
     /**
@@ -762,9 +774,6 @@
      * @hide
      */
     public static class VectorDrawableAnimator {
-        private static final int NONE = 0;
-        private static final int START_ANIMATION = 1;
-        private static final int REVERSE_ANIMATION = 2;
         private AnimatorListener mListener = null;
         private final LongArray mStartDelays = new LongArray();
         private PropertyValuesHolder.PropertyValues mTmpValues =
@@ -773,6 +782,7 @@
         private boolean mContainsSequentialAnimators = false;
         private boolean mStarted = false;
         private boolean mInitialized = false;
+        private boolean mAnimationPending = false;
         private boolean mIsReversible = false;
         // This needs to be set before parsing starts.
         private boolean mShouldIgnoreInvalidAnim;
@@ -780,8 +790,7 @@
         private final VirtualRefBasePtr mSetRefBasePtr;
         private WeakReference<RenderNode> mTarget = null;
         private WeakReference<RenderNode> mLastSeenTarget = null;
-        private int mLastListenerId = 0;
-        private int mPendingAnimationAction = NONE;
+
 
         VectorDrawableAnimator() {
             mSetPtr = nCreateAnimatorSet();
@@ -801,7 +810,6 @@
             mInitialized = true;
 
             // Check reversible.
-            mIsReversible = true;
             if (mContainsSequentialAnimators) {
                 mIsReversible = false;
             } else {
@@ -813,6 +821,7 @@
                     }
                 }
             }
+            mIsReversible = true;
         }
 
         private void parseAnimatorSet(AnimatorSet set, long startTime) {
@@ -1033,22 +1042,27 @@
          * to the last seen RenderNode target and start right away.
          */
         protected void recordLastSeenTarget(DisplayListCanvas canvas) {
-            mLastSeenTarget = new WeakReference<RenderNode>(
-                    RenderNodeAnimatorSetHelper.getTarget(canvas));
-            if (mPendingAnimationAction != NONE) {
+            if (mAnimationPending) {
+                mLastSeenTarget = new WeakReference<RenderNode>(
+                        RenderNodeAnimatorSetHelper.getTarget(canvas));
                 if (DBG_ANIMATION_VECTOR_DRAWABLE) {
                     Log.d(LOGTAG, "Target is set in the next frame");
                 }
-                if (mPendingAnimationAction == START_ANIMATION) {
-                    start();
-                } else if (mPendingAnimationAction == REVERSE_ANIMATION) {
-                    reverse();
-                }
-                mPendingAnimationAction = NONE;
+                mAnimationPending = false;
+                start();
+            } else {
+                mLastSeenTarget = new WeakReference<RenderNode>(
+                        RenderNodeAnimatorSetHelper.getTarget(canvas));
             }
+
         }
 
         private boolean setTarget(RenderNode node) {
+            if (mTarget != null && mTarget.get() != null) {
+                // TODO: Maybe we want to support target change.
+                throw new IllegalStateException("Target already set!");
+            }
+
             node.addAnimator(this);
             mTarget = new WeakReference<RenderNode>(node);
             return true;
@@ -1067,8 +1081,12 @@
                 return;
             }
 
+            if (mStarted) {
+                return;
+            }
+
             if (!useLastSeenTarget()) {
-                mPendingAnimationAction = START_ANIMATION;
+                mAnimationPending = true;
                 return;
             }
 
@@ -1076,45 +1094,38 @@
                 Log.d(LOGTAG, "Target is set. Starting VDAnimatorSet from java");
             }
 
-            mStarted = true;
-            nStart(mSetPtr, this, ++mLastListenerId);
+           nStart(mSetPtr, this);
             if (mListener != null) {
                 mListener.onAnimationStart(null);
             }
+            mStarted = true;
         }
 
         public void end() {
-            if (mInitialized && useLastSeenTarget()) {
-                // If no target has ever been set, no-op
+            if (mInitialized && mStarted) {
                 nEnd(mSetPtr);
+                onAnimationEnd();
             }
         }
 
-        public void reset() {
-            if (mInitialized && useLastSeenTarget()) {
-                // If no target has ever been set, no-op
-                nReset(mSetPtr);
+        void reset() {
+            if (!mInitialized) {
+                return;
             }
+            // TODO: Need to implement reset.
+            Log.w(LOGTAG, "Reset is yet to be implemented");
+            nReset(mSetPtr);
         }
 
         // Current (imperfect) Java AnimatorSet cannot be reversed when the set contains sequential
         // animators or when the animator set has a start delay
         void reverse() {
-            if (!mIsReversible || !mInitialized) {
+            if (!mIsReversible) {
                 return;
             }
-            if (!useLastSeenTarget()) {
-                mPendingAnimationAction = REVERSE_ANIMATION;
-                return;
-            }
-            if (DBG_ANIMATION_VECTOR_DRAWABLE) {
-                Log.d(LOGTAG, "Target is set. Reversing VDAnimatorSet from java");
-            }
-            mStarted = true;
-            nReverse(mSetPtr, this, ++mLastListenerId);
-            if (mListener != null) {
-                mListener.onAnimationStart(null);
-            }
+            // TODO: Need to support reverse (non-public API)
+            Log.w(LOGTAG, "Reverse is yet to be implemented");
+            nReverse(mSetPtr, this);
         }
 
         public long getAnimatorNativePtr() {
@@ -1144,13 +1155,7 @@
             mListener = null;
         }
 
-        private void onAnimationEnd(int listenerId) {
-            if (listenerId != mLastListenerId) {
-                return;
-            }
-            if (DBG_ANIMATION_VECTOR_DRAWABLE) {
-                Log.d(LOGTAG, "on finished called from native");
-            }
+        private void onAnimationEnd() {
             mStarted = false;
             if (mListener != null) {
                 mListener.onAnimationEnd(null);
@@ -1159,8 +1164,11 @@
         }
 
         // onFinished: should be called from native
-        private static void callOnFinished(VectorDrawableAnimator set, int id) {
-            set.onAnimationEnd(id);
+        private static void callOnFinished(VectorDrawableAnimator set) {
+            if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+                Log.d(LOGTAG, "on finished called from native");
+            }
+            set.onAnimationEnd();
         }
     }
 
@@ -1180,8 +1188,8 @@
     private static native long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue,
             float endValue);
     private static native void nSetPropertyHolderData(long nativePtr, float[] data, int length);
-    private static native void nStart(long animatorSetPtr, VectorDrawableAnimator set, int id);
-    private static native void nReverse(long animatorSetPtr, VectorDrawableAnimator set, int id);
+    private static native void nStart(long animatorSetPtr, VectorDrawableAnimator set);
+    private static native void nReverse(long animatorSetPtr, VectorDrawableAnimator set);
     private static native void nEnd(long animatorSetPtr);
     private static native void nReset(long animatorSetPtr);
 }
diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp
index 372bcb3..7bd2b24 100644
--- a/libs/hwui/Animator.cpp
+++ b/libs/hwui/Animator.cpp
@@ -42,8 +42,7 @@
         , mStartTime(0)
         , mDuration(300)
         , mStartDelay(0)
-        , mMayRunAsync(true)
-        , mPlayTime(0) {
+        , mMayRunAsync(true) {
 }
 
 BaseRenderNodeAnimator::~BaseRenderNodeAnimator() {
@@ -86,113 +85,20 @@
     onAttached();
 }
 
-void BaseRenderNodeAnimator::start() {
-    mStagingPlayState = PlayState::Running;
-    mStagingRequests.push_back(Request::Start);
-    onStagingPlayStateChanged();
-}
-
-void BaseRenderNodeAnimator::cancel() {
-    mStagingPlayState = PlayState::Finished;
-    mStagingRequests.push_back(Request::Cancel);
-    onStagingPlayStateChanged();
-}
-
-void BaseRenderNodeAnimator::reset() {
-    mStagingPlayState = PlayState::Finished;
-    mStagingRequests.push_back(Request::Reset);
-    onStagingPlayStateChanged();
-}
-
-void BaseRenderNodeAnimator::reverse() {
-    mStagingPlayState = PlayState::Reversing;
-    mStagingRequests.push_back(Request::Reverse);
-    onStagingPlayStateChanged();
-}
-
-void BaseRenderNodeAnimator::end() {
-    mStagingPlayState = PlayState::Finished;
-    mStagingRequests.push_back(Request::End);
-    onStagingPlayStateChanged();
-}
-
-void BaseRenderNodeAnimator::resolveStagingRequest(Request request) {
-    switch (request) {
-    case Request::Start:
-        mPlayTime = (mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) ?
-                        mPlayTime : 0;
-        mPlayState = PlayState::Running;
-        break;
-    case Request::Reverse:
-        mPlayTime = (mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) ?
-                        mPlayTime : mDuration;
-        mPlayState = PlayState::Reversing;
-        break;
-    case Request::Reset:
-        mPlayTime = 0;
-        mPlayState = PlayState::Finished;
-        break;
-    case Request::Cancel:
-        mPlayState = PlayState::Finished;
-        break;
-    case Request::End:
-        mPlayTime = mPlayState == PlayState::Reversing ? 0 : mDuration;
-        mPlayState = PlayState::Finished;
-        break;
-    default:
-        LOG_ALWAYS_FATAL("Invalid staging request: %d", static_cast<int>(request));
-    };
-}
-
 void BaseRenderNodeAnimator::pushStaging(AnimationContext& context) {
     if (!mHasStartValue) {
         doSetStartValue(getValue(mTarget));
     }
-
-    if (!mStagingRequests.empty()) {
-        // Keep track of the play state and play time before they are changed when
-        // staging requests are resolved.
-        nsecs_t currentPlayTime = mPlayTime;
-        PlayState prevFramePlayState = mPlayState;
-
-        // Resolve staging requests one by one.
-        for (Request request : mStagingRequests) {
-            resolveStagingRequest(request);
+    if (mStagingPlayState > mPlayState) {
+        if (mStagingPlayState == PlayState::Restarted) {
+            mStagingPlayState = PlayState::Running;
         }
-        mStagingRequests.clear();
-
-        if (mStagingPlayState == PlayState::Finished) {
-            // Set the staging play time and end the animation
-            updatePlayTime(mPlayTime);
+        mPlayState = mStagingPlayState;
+        // Oh boy, we're starting! Man the battle stations!
+        if (mPlayState == PlayState::Running) {
+            transitionToRunning(context);
+        } else if (mPlayState == PlayState::Finished) {
             callOnFinishedListener(context);
-        } else if (mStagingPlayState == PlayState::Running
-                || mStagingPlayState == PlayState::Reversing) {
-            bool changed = currentPlayTime != mPlayTime || prevFramePlayState != mStagingPlayState;
-            if (prevFramePlayState != mStagingPlayState) {
-                transitionToRunning(context);
-            }
-            if (changed) {
-                // Now we need to seek to the stagingPlayTime (i.e. the animation progress that was
-                // requested from UI thread). It is achieved by modifying mStartTime, such that
-                // current time - mStartTime = stagingPlayTime (or mDuration -stagingPlayTime in the
-                // case of reversing)
-                nsecs_t currentFrameTime = context.frameTimeMs();
-                if (mPlayState == PlayState::Reversing) {
-                    // Reverse is not supported for animations with a start delay, so here we
-                    // assume no start delay.
-                    mStartTime = currentFrameTime  - (mDuration - mPlayTime);
-                } else {
-                    // Animation should play forward
-                    if (mPlayTime == 0) {
-                        // If the request is to start from the beginning, include start delay.
-                        mStartTime = currentFrameTime + mStartDelay;
-                    } else {
-                        // If the request is to seek to a non-zero play time, then we skip start
-                        // delay.
-                        mStartTime = currentFrameTime - mPlayTime;
-                    }
-                }
-            }
         }
     }
 }
@@ -230,37 +136,37 @@
 
     // This should be set before setValue() so animators can query this time when setValue
     // is called.
-    nsecs_t currentPlayTime = context.frameTimeMs() - mStartTime;
-    bool finished = updatePlayTime(currentPlayTime);
-    if (finished && mPlayState != PlayState::Finished) {
-        mPlayState = PlayState::Finished;
-        callOnFinishedListener(context);
-    }
-    return finished;
-}
+    nsecs_t currentFrameTime = context.frameTimeMs();
+    onPlayTimeChanged(currentFrameTime - mStartTime);
 
-bool BaseRenderNodeAnimator::updatePlayTime(nsecs_t playTime) {
-    mPlayTime = mPlayState == PlayState::Reversing ? mDuration - playTime : playTime;
-    onPlayTimeChanged(mPlayTime);
     // If BaseRenderNodeAnimator is handling the delay (not typical), then
     // because the staging properties reflect the final value, we always need
     // to call setValue even if the animation isn't yet running or is still
     // being delayed as we need to override the staging value
-    if (playTime < 0) {
+    if (mStartTime > context.frameTimeMs()) {
         setValue(mTarget, mFromValue);
         return false;
     }
 
     float fraction = 1.0f;
-    if ((mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) && mDuration > 0) {
-        fraction = mPlayTime / (float) mDuration;
+
+    if (mPlayState == PlayState::Running && mDuration > 0) {
+        fraction = (float)(currentFrameTime - mStartTime) / mDuration;
     }
-    fraction = MathUtils::clamp(fraction, 0.0f, 1.0f);
+    if (fraction >= 1.0f) {
+        fraction = 1.0f;
+        mPlayState = PlayState::Finished;
+    }
 
     fraction = mInterpolator->interpolate(fraction);
     setValue(mTarget, mFromValue + (mDeltaValue * fraction));
 
-    return playTime >= mDuration;
+    if (mPlayState == PlayState::Finished) {
+        callOnFinishedListener(context);
+        return true;
+    }
+
+    return false;
 }
 
 void BaseRenderNodeAnimator::forceEndNow(AnimationContext& context) {
diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h
index fcbc11b..2c9c9c3 100644
--- a/libs/hwui/Animator.h
+++ b/libs/hwui/Animator.h
@@ -24,8 +24,6 @@
 
 #include "utils/Macros.h"
 
-#include <vector>
-
 namespace android {
 namespace uirenderer {
 
@@ -61,14 +59,14 @@
         mMayRunAsync = mayRunAsync;
     }
     bool mayRunAsync() { return mMayRunAsync; }
-    ANDROID_API void start();
-    ANDROID_API void reset();
-    ANDROID_API void reverse();
-    // Terminates the animation at its current progress.
-    ANDROID_API void cancel();
-
-    // Terminates the animation and skip to the end of the animation.
-    ANDROID_API void end();
+    ANDROID_API void start() {
+        if (mStagingPlayState == PlayState::NotStarted) {
+            mStagingPlayState = PlayState::Running;
+        } else {
+            mStagingPlayState = PlayState::Restarted;
+        }
+        onStagingPlayStateChanged(); }
+    ANDROID_API void end() { mStagingPlayState = PlayState::Finished; onStagingPlayStateChanged(); }
 
     void attach(RenderNode* target);
     virtual void onAttached() {}
@@ -76,41 +74,36 @@
     void pushStaging(AnimationContext& context);
     bool animate(AnimationContext& context);
 
-    bool isRunning() { return mPlayState == PlayState::Running
-            || mPlayState == PlayState::Reversing; }
+    bool isRunning() { return mPlayState == PlayState::Running; }
     bool isFinished() { return mPlayState == PlayState::Finished; }
     float finalValue() { return mFinalValue; }
 
     ANDROID_API virtual uint32_t dirtyMask() = 0;
 
     void forceEndNow(AnimationContext& context);
-    RenderNode* target() { return mTarget; }
 
 protected:
     // PlayState is used by mStagingPlayState and mPlayState to track the state initiated from UI
     // thread and Render Thread animation state, respectively.
     // From the UI thread, mStagingPlayState transition looks like
-    // NotStarted -> Running/Reversing -> Finished
-    //                ^                     |
-    //                |                     |
-    //                ----------------------
+    // NotStarted -> Running -> Finished
+    //                ^            |
+    //                |            |
+    //            Restarted <------
     // Note: For mStagingState, the Finished state (optional) is only set when the animation is
     // terminated by user.
     //
     // On Render Thread, mPlayState transition:
-    // NotStart -> Running/Reversing-> Finished
-    //                ^                 |
-    //                |                 |
-    //                ------------------
-    // Note that if the animation is in Running/Reversing state, calling start or reverse again
-    // would do nothing if the animation has the same play direction as the request; otherwise,
-    // the animation would start from where it is and change direction (i.e. Reversing <-> Running)
+    // NotStart -> Running -> Finished
+    //                ^            |
+    //                |            |
+    //                -------------
 
     enum class PlayState {
         NotStarted,
         Running,
-        Reversing,
         Finished,
+        Restarted,
     };
 
     BaseRenderNodeAnimator(float finalValue);
@@ -118,6 +111,7 @@
 
     virtual float getValue(RenderNode* target) const = 0;
     virtual void setValue(RenderNode* target, float value) = 0;
+    RenderNode* target() { return mTarget; }
 
     void callOnFinishedListener(AnimationContext& context);
 
@@ -138,28 +132,13 @@
     nsecs_t mDuration;
     nsecs_t mStartDelay;
     bool mMayRunAsync;
-    // Play Time tracks the progress of animation, it should always be [0, mDuration], 0 being
-    // the beginning of the animation, will reach mDuration at the end of an animation.
-    nsecs_t mPlayTime;
 
     sp<AnimationListener> mListener;
 
 private:
-    enum class Request {
-        Start,
-        Reverse,
-        Reset,
-        Cancel,
-        End
-    };
     inline void checkMutable();
     virtual void transitionToRunning(AnimationContext& context);
     void doSetStartValue(float value);
-    bool updatePlayTime(nsecs_t playTime);
-    void resolveStagingRequest(Request request);
-
-    std::vector<Request> mStagingRequests;
-
 };
 
 class RenderPropertyAnimator : public BaseRenderNodeAnimator {
diff --git a/libs/hwui/AnimatorManager.cpp b/libs/hwui/AnimatorManager.cpp
index 2b49b47..cd30b18 100644
--- a/libs/hwui/AnimatorManager.cpp
+++ b/libs/hwui/AnimatorManager.cpp
@@ -27,8 +27,9 @@
 
 using namespace std;
 
-static void detach(sp<BaseRenderNodeAnimator>& animator) {
+static void unref(BaseRenderNodeAnimator* animator) {
     animator->detach();
+    animator->decStrong(nullptr);
 }
 
 AnimatorManager::AnimatorManager(RenderNode& parent)
@@ -37,12 +38,14 @@
 }
 
 AnimatorManager::~AnimatorManager() {
-    for_each(mNewAnimators.begin(), mNewAnimators.end(), detach);
-    for_each(mAnimators.begin(), mAnimators.end(), detach);
+    for_each(mNewAnimators.begin(), mNewAnimators.end(), unref);
+    for_each(mAnimators.begin(), mAnimators.end(), unref);
 }
 
 void AnimatorManager::addAnimator(const sp<BaseRenderNodeAnimator>& animator) {
-    mNewAnimators.emplace_back(animator.get());
+    animator->incStrong(nullptr);
+    animator->attach(&mParent);
+    mNewAnimators.push_back(animator.get());
 }
 
 void AnimatorManager::setAnimationHandle(AnimationHandle* handle) {
@@ -53,31 +56,25 @@
             &mParent, mParent.getName());
 }
 
+template<typename T>
+static void move_all(T& source, T& dest) {
+    dest.reserve(source.size() + dest.size());
+    for (typename T::iterator it = source.begin(); it != source.end(); it++) {
+        dest.push_back(*it);
+    }
+    source.clear();
+}
+
 void AnimatorManager::pushStaging() {
     if (mNewAnimators.size()) {
         LOG_ALWAYS_FATAL_IF(!mAnimationHandle,
                 "Trying to start new animators on %p (%s) without an animation handle!",
                 &mParent, mParent.getName());
-        // Only add animators that are not already in the on-going animator list.
-        for (auto& animator : mNewAnimators) {
-            RenderNode* targetRenderNode = animator->target();
-            if (targetRenderNode == &mParent) {
-                // Animator already in the animator list: skip adding again
-                continue;
-            }
-
-            if (targetRenderNode){
-                // If the animator is already in another RenderNode's animator list, remove animator from
-                // that list and add animator to current RenderNode's list.
-                targetRenderNode->animators().removeActiveAnimator(animator);
-            }
-            animator->attach(&mParent);
-            mAnimators.push_back(std::move(animator));
-        }
-        mNewAnimators.clear();
+        // Since this is a straight move, we don't need to inc/dec the ref count
+        move_all(mNewAnimators, mAnimators);
     }
-    for (auto& animator : mAnimators) {
-        animator->pushStaging(mAnimationHandle->context());
+    for (vector<BaseRenderNodeAnimator*>::iterator it = mAnimators.begin(); it != mAnimators.end(); it++) {
+        (*it)->pushStaging(mAnimationHandle->context());
     }
 }
 
@@ -86,11 +83,11 @@
     AnimateFunctor(TreeInfo& info, AnimationContext& context)
             : dirtyMask(0), mInfo(info), mContext(context) {}
 
-    bool operator() (sp<BaseRenderNodeAnimator>& animator) {
+    bool operator() (BaseRenderNodeAnimator* animator) {
         dirtyMask |= animator->dirtyMask();
         bool remove = animator->animate(mContext);
         if (remove) {
-            animator->detach();
+            animator->decStrong(nullptr);
         } else {
             if (animator->isRunning()) {
                 mInfo.out.hasAnimations = true;
@@ -132,18 +129,20 @@
 
 uint32_t AnimatorManager::animateCommon(TreeInfo& info) {
     AnimateFunctor functor(info, mAnimationHandle->context());
-    auto newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor);
+    std::vector< BaseRenderNodeAnimator* >::iterator newEnd;
+    newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor);
     mAnimators.erase(newEnd, mAnimators.end());
     mAnimationHandle->notifyAnimationsRan();
     mParent.mProperties.updateMatrix();
     return functor.dirtyMask;
 }
 
-static void endStagingAnimator(sp<BaseRenderNodeAnimator>& animator) {
-    animator->cancel();
+static void endStagingAnimator(BaseRenderNodeAnimator* animator) {
+    animator->end();
     if (animator->listener()) {
-        animator->listener()->onAnimationFinished(animator.get());
+        animator->listener()->onAnimationFinished(animator);
     }
+    animator->decStrong(nullptr);
 }
 
 void AnimatorManager::endAllStagingAnimators() {
@@ -154,16 +153,13 @@
     mNewAnimators.clear();
 }
 
-void AnimatorManager::removeActiveAnimator(const sp<BaseRenderNodeAnimator>& animator) {
-    std::remove(mAnimators.begin(), mAnimators.end(), animator);
-}
-
 class EndActiveAnimatorsFunctor {
 public:
     EndActiveAnimatorsFunctor(AnimationContext& context) : mContext(context) {}
 
-    void operator() (sp<BaseRenderNodeAnimator>& animator) {
+    void operator() (BaseRenderNodeAnimator* animator) {
         animator->forceEndNow(mContext);
+        animator->decStrong(nullptr);
     }
 
 private:
diff --git a/libs/hwui/AnimatorManager.h b/libs/hwui/AnimatorManager.h
index c24ef47..fb75eb8 100644
--- a/libs/hwui/AnimatorManager.h
+++ b/libs/hwui/AnimatorManager.h
@@ -62,17 +62,13 @@
 private:
     uint32_t animateCommon(TreeInfo& info);
 
-    // This would remove the animator from mAnimators list. It should only be called during
-    // push staging.
-    void removeActiveAnimator(const sp<BaseRenderNodeAnimator>& animator);
-
     RenderNode& mParent;
     AnimationHandle* mAnimationHandle;
 
     // To improve the efficiency of resizing & removing from the vector
     // use manual ref counting instead of sp<>.
-    std::vector< sp<BaseRenderNodeAnimator> > mNewAnimators;
-    std::vector< sp<BaseRenderNodeAnimator> > mAnimators;
+    std::vector<BaseRenderNodeAnimator*> mNewAnimators;
+    std::vector<BaseRenderNodeAnimator*> mAnimators;
 };
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/PropertyValuesAnimatorSet.cpp b/libs/hwui/PropertyValuesAnimatorSet.cpp
index b29f91f..eca1afcc 100644
--- a/libs/hwui/PropertyValuesAnimatorSet.cpp
+++ b/libs/hwui/PropertyValuesAnimatorSet.cpp
@@ -17,8 +17,6 @@
 #include "PropertyValuesAnimatorSet.h"
 #include "RenderNode.h"
 
-#include <algorithm>
-
 namespace android {
 namespace uirenderer {
 
@@ -55,26 +53,16 @@
 }
 
 void PropertyValuesAnimatorSet::onPlayTimeChanged(nsecs_t playTime) {
-    if (playTime == 0 && mDuration > 0) {
-        // Reset all the animators
-        for (auto it = mAnimators.rbegin(); it != mAnimators.rend(); it++) {
-            // Note that this set may containing animators modifying the same property, so when we
-            // reset the animators, we need to make sure the animators that end the first will
-            // have the final say on what the property value should be.
-            (*it)->setFraction(0);
-        }
-    } else if (playTime >= mDuration) {
-        // Skip all the animators to end
-        for (auto& anim : mAnimators) {
-            anim->setFraction(1);
-        }
-    } else {
-        for (auto& anim : mAnimators) {
-            anim->setCurrentPlayTime(playTime);
-        }
+    for (size_t i = 0; i < mAnimators.size(); i++) {
+        mAnimators[i]->setCurrentPlayTime(playTime);
     }
 }
 
+void PropertyValuesAnimatorSet::reset() {
+    // TODO: implement reset through adding a play state because we need to support reset() even
+    // during an animation run.
+}
+
 void PropertyValuesAnimatorSet::start(AnimationListener* listener) {
     init();
     mOneShotListener = listener;
@@ -82,23 +70,20 @@
 }
 
 void PropertyValuesAnimatorSet::reverse(AnimationListener* listener) {
-    init();
-    mOneShotListener = listener;
-    BaseRenderNodeAnimator::reverse();
+// TODO: implement reverse
 }
 
 void PropertyValuesAnimatorSet::init() {
     if (mInitialized) {
         return;
     }
-
-    // Sort the animators by their total duration. Note that all the animators in the set start at
-    // the same time, so the ones with longer total duration (which includes start delay) will
-    // be the ones that end later.
-    std::sort(mAnimators.begin(), mAnimators.end(), [](auto& a, auto&b) {
-        return a->getTotalDuration() < b->getTotalDuration();
-    });
-    mDuration = mAnimators[mAnimators.size() - 1]->getTotalDuration();
+    nsecs_t maxDuration = 0;
+    for (size_t i = 0; i < mAnimators.size(); i++) {
+        if (maxDuration < mAnimators[i]->getTotalDuration()) {
+            maxDuration = mAnimators[i]->getTotalDuration();
+        }
+    }
+    mDuration = maxDuration;
     mInitialized = true;
 }
 
@@ -121,19 +106,18 @@
 void PropertyAnimator::setCurrentPlayTime(nsecs_t playTime) {
     if (playTime >= mStartDelay && playTime < mTotalDuration) {
          nsecs_t currentIterationPlayTime = (playTime - mStartDelay) % mDuration;
-         float fraction = currentIterationPlayTime / (float) mDuration;
-         setFraction(fraction);
+         mLatestFraction = currentIterationPlayTime / (float) mDuration;
     } else if (mLatestFraction < 1.0f && playTime >= mTotalDuration) {
-        // This makes sure we only set the fraction = 1 once. It is needed because there might
-        // be another animator modifying the same property after this animator finishes, we need
-        // to make sure we don't set conflicting values on the same property within one frame.
-        setFraction(1.0f);
+        mLatestFraction = 1.0f;
+    } else {
+        return;
     }
+
+    setFraction(mLatestFraction);
 }
 
 void PropertyAnimator::setFraction(float fraction) {
-    mLatestFraction = fraction;
-    float interpolatedFraction = mInterpolator->interpolate(fraction);
+    float interpolatedFraction = mInterpolator->interpolate(mLatestFraction);
     mPropertyValuesHolder->setFraction(interpolatedFraction);
 }
 
diff --git a/libs/hwui/PropertyValuesAnimatorSet.h b/libs/hwui/PropertyValuesAnimatorSet.h
index c7ae7c0..4c7ce52 100644
--- a/libs/hwui/PropertyValuesAnimatorSet.h
+++ b/libs/hwui/PropertyValuesAnimatorSet.h
@@ -50,6 +50,7 @@
 
     void start(AnimationListener* listener);
     void reverse(AnimationListener* listener);
+    void reset();
 
     void addPropertyAnimator(PropertyValuesHolder* propertyValuesHolder,
             Interpolator* interpolators, int64_t startDelays,
diff --git a/packages/DocumentsUI/res/layout/fixed_layout.xml b/packages/DocumentsUI/res/layout/fixed_layout.xml
index 8414feb..84a928d 100644
--- a/packages/DocumentsUI/res/layout/fixed_layout.xml
+++ b/packages/DocumentsUI/res/layout/fixed_layout.xml
@@ -16,11 +16,14 @@
 
 <!-- CoordinatorLayout is necessary for various components (e.g. Snackbars, and
      floating action buttons) to operate correctly. -->
+<!-- focusableInTouchMode is set in order to force key events to go to the activity's global key
+     callback, which is necessary for proper event routing. See BaseActivity.onKeyDown. -->
 <android.support.design.widget.CoordinatorLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:id="@+id/coordinator_layout">
+    android:id="@+id/coordinator_layout"
+    android:focusableInTouchMode="true">
 
     <LinearLayout
         android:layout_width="match_parent"
diff --git a/packages/DocumentsUI/res/layout/fragment_directory.xml b/packages/DocumentsUI/res/layout/fragment_directory.xml
index d0364ff..0fb74e5 100644
--- a/packages/DocumentsUI/res/layout/fragment_directory.xml
+++ b/packages/DocumentsUI/res/layout/fragment_directory.xml
@@ -83,7 +83,7 @@
         android:layout_height="match_parent">
 
         <android.support.v7.widget.RecyclerView
-            android:id="@+id/list"
+            android:id="@+id/dir_list"
             android:scrollbars="vertical"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
diff --git a/packages/DocumentsUI/res/layout/fragment_roots.xml b/packages/DocumentsUI/res/layout/fragment_roots.xml
index f3de3b4..b33b8d0 100644
--- a/packages/DocumentsUI/res/layout/fragment_roots.xml
+++ b/packages/DocumentsUI/res/layout/fragment_roots.xml
@@ -14,8 +14,8 @@
      limitations under the License.
 -->
 
-<ListView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@android:id/list"
+<com.android.documentsui.RootsList xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/roots_list"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:paddingTop="8dp"
diff --git a/packages/DocumentsUI/res/layout/single_pane_layout.xml b/packages/DocumentsUI/res/layout/single_pane_layout.xml
index f53d698..235d22d 100644
--- a/packages/DocumentsUI/res/layout/single_pane_layout.xml
+++ b/packages/DocumentsUI/res/layout/single_pane_layout.xml
@@ -16,11 +16,14 @@
 
 <!-- CoordinatorLayout is necessary for various components (e.g. Snackbars, and
      floating action buttons) to operate correctly. -->
+<!-- focusableInTouchMode is set in order to force key events to go to the activity's global key 
+     callback, which is necessary for proper event routing. See BaseActivity.onKeyDown. -->
 <android.support.design.widget.CoordinatorLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:id="@+id/coordinator_layout">
+    android:id="@+id/coordinator_layout"
+    android:focusableInTouchMode="true">
 
     <LinearLayout
         android:layout_width="match_parent"
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 475387b..4a55906 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -42,6 +42,7 @@
 import android.support.annotation.LayoutRes;
 import android.support.annotation.Nullable;
 import android.util.Log;
+import android.view.KeyEvent;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.widget.Spinner;
@@ -83,6 +84,8 @@
     // We use the time gap to figure out whether to close app or reopen the drawer.
     private long mDrawerLastFiddled;
 
+    private boolean mNavDrawerHasFocus;
+
     public abstract void onDocumentPicked(DocumentInfo doc, @Nullable SiblingProvider siblings);
     public abstract void onDocumentsPicked(List<DocumentInfo> docs);
 
@@ -580,6 +583,54 @@
         }
     }
 
+    /**
+     * Declare a global key handler to route key events when there isn't a specific focus view. This
+     * covers the scenario where a user opens DocumentsUI and just starts typing.
+     *
+     * @param keyCode
+     * @param event
+     * @return
+     */
+    @CallSuper
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (Events.isNavigationKeyCode(keyCode)) {
+            // Forward all unclaimed navigation keystrokes to the DirectoryFragment. This causes any
+            // stray navigation keystrokes focus the content pane, which is probably what the user
+            // is trying to do.
+            DirectoryFragment df = DirectoryFragment.get(getFragmentManager());
+            if (df != null) {
+                df.requestFocus();
+                return true;
+            }
+        } else if (keyCode == KeyEvent.KEYCODE_TAB) {
+            toggleNavDrawerFocus();
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    /**
+     * Toggles focus between the navigation drawer and the directory listing. If the drawer isn't
+     * locked, open/close it as appropriate.
+     */
+    void toggleNavDrawerFocus() {
+        if (mNavDrawerHasFocus) {
+            mDrawer.setOpen(false);
+            DirectoryFragment df = DirectoryFragment.get(getFragmentManager());
+            if (df != null) {
+                df.requestFocus();
+            }
+        } else {
+            mDrawer.setOpen(true);
+            RootsFragment rf = RootsFragment.get(getFragmentManager());
+            if (rf != null) {
+                rf.requestFocus();
+            }
+        }
+        mNavDrawerHasFocus = !mNavDrawerHasFocus;
+    }
+
     DocumentInfo getRootDocumentBlocking(RootInfo root) {
         try {
             final Uri uri = DocumentsContract.buildDocumentUri(
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
index 7dac0c1..0e27622 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
@@ -83,7 +83,7 @@
 
         final View view = inflater.inflate(R.layout.fragment_directory, container, false);
 
-        mRecView = (RecyclerView) view.findViewById(R.id.list);
+        mRecView = (RecyclerView) view.findViewById(R.id.dir_list);
         mRecView.setLayoutManager(new LinearLayoutManager(getContext()));
         mRecView.addOnItemTouchListener(mItemListener);
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index 26bda31..53f8297 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -89,7 +89,7 @@
         final Context context = inflater.getContext();
 
         final View view = inflater.inflate(R.layout.fragment_roots, container, false);
-        mList = (ListView) view.findViewById(android.R.id.list);
+        mList = (ListView) view.findViewById(R.id.roots_list);
         mList.setOnItemClickListener(mItemListener);
         mList.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
         return view;
@@ -167,6 +167,13 @@
         }
     }
 
+    /**
+     * Attempts to shift focus back to the navigation drawer.
+     */
+    public void requestFocus() {
+        mList.requestFocus();
+    }
+
     private void showAppDetails(ResolveInfo ri) {
         final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
         intent.setData(Uri.fromParts("package", ri.activityInfo.packageName, null));
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsList.java b/packages/DocumentsUI/src/com/android/documentsui/RootsList.java
new file mode 100644
index 0000000..bf03ffd
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsList.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2016 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.documentsui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.widget.ListView;
+
+/**
+ * The list in the navigation drawer. This class exists for the purpose of overriding the key
+ * handler on ListView. Ignoring keystrokes (e.g. the tab key) cannot be properly done using
+ * View.OnKeyListener.
+ */
+public class RootsList extends ListView {
+
+    // Multiple constructors are needed to handle all the different ways this View could be
+    // constructed by the framework. Don't remove them!
+    public RootsList(Context context) {
+        super(context);
+    }
+
+    public RootsList(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    public RootsList(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public RootsList(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+            // Ignore tab key events - this causes them to bubble up to the global key handler where
+            // they are appropriately handled. See BaseActivity.onKeyDown.
+            case KeyEvent.KEYCODE_TAB:
+                return false;
+            // Prevent left/right arrow keystrokes from shifting focus away from the roots list.
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                return true;
+            default:
+                return super.onKeyDown(keyCode, event);
+        }
+    }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 174984c..726538e 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -183,7 +183,7 @@
 
         mEmptyView = view.findViewById(android.R.id.empty);
 
-        mRecView = (RecyclerView) view.findViewById(R.id.list);
+        mRecView = (RecyclerView) view.findViewById(R.id.dir_list);
         mRecView.setRecyclerListener(
                 new RecyclerListener() {
                     @Override
@@ -263,6 +263,7 @@
 
         mSelectionManager.addCallback(selectionListener);
 
+        // Make sure this is done after the RecyclerView is set up.
         mFocusManager = new FocusManager(mRecView, mSelectionManager);
 
         mModel = new Model();
@@ -834,6 +835,7 @@
     @Override
     public void initDocumentHolder(DocumentHolder holder) {
         holder.addEventListener(mItemEventListener);
+        holder.itemView.setOnFocusChangeListener(mFocusManager);
     }
 
     @Override
@@ -1054,6 +1056,13 @@
         }
     }
 
+    /**
+     * Attempts to restore focus on the directory listing.
+     */
+    public void requestFocus() {
+        mFocusManager.restoreLastFocus();
+    }
+
     private void setupDragAndDropOnDirectoryView(View view) {
         // Listen for drops on non-directory items and empty space.
         view.setOnDragListener(mOnDragListener);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java
index 86b9146..ad010a6 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java
@@ -27,7 +27,7 @@
 /**
  * A class that handles navigation and focus within the DirectoryFragment.
  */
-class FocusManager {
+class FocusManager implements View.OnFocusChangeListener {
     private static final String TAG = "FocusManager";
 
     private RecyclerView mView;
@@ -35,6 +35,8 @@
     private LinearLayoutManager mLayout;
     private MultiSelectManager mSelectionManager;
 
+    private int mLastFocusPosition = RecyclerView.NO_POSITION;
+
     public FocusManager(RecyclerView view, MultiSelectManager selectionManager) {
         mView = view;
         mAdapter = view.getAdapter();
@@ -52,24 +54,46 @@
      * @return Whether the event was handled.
      */
     public boolean handleKey(DocumentHolder doc, int keyCode, KeyEvent event) {
-        boolean handled = false;
         if (Events.isNavigationKeyCode(keyCode)) {
             // Find the target item and focus it.
             int endPos = findTargetPosition(doc.itemView, keyCode, event);
 
             if (endPos != RecyclerView.NO_POSITION) {
                 focusItem(endPos);
+                boolean extendSelection = event.isShiftPressed();
 
                 // Handle any necessary adjustments to selection.
-                boolean extendSelection = event.isShiftPressed();
                 if (extendSelection) {
                     int startPos = doc.getAdapterPosition();
                     mSelectionManager.selectRange(startPos, endPos);
                 }
-                handled = true;
             }
+            // Swallow all navigation keystrokes. Otherwise they go to the app's global
+            // key-handler, which will route them back to the DF and cause focus to be reset.
+            return true;
         }
-        return handled;
+        return false;
+    }
+
+    @Override
+    public void onFocusChange(View v, boolean hasFocus) {
+        // Remember focus events on items.
+        if (hasFocus && v.getParent() == mView) {
+            mLastFocusPosition = mView.getChildAdapterPosition(v);
+        }
+    }
+
+    /**
+     * Requests focus on the item that last had focus. Scrolls to that item if necessary.
+     */
+    public void restoreLastFocus() {
+        if (mLastFocusPosition != RecyclerView.NO_POSITION) {
+            // The system takes care of situations when a view is no longer on screen, etc,
+            focusItem(mLastFocusPosition);
+        } else {
+            // Focus the first visible item
+            focusItem(mLayout.findFirstVisibleItemPosition());
+        }
     }
 
     /**
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
index 77f16d9..609dc0c 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
@@ -21,6 +21,7 @@
 
 import android.os.RemoteException;
 import android.test.suitebuilder.annotation.LargeTest;
+import android.view.KeyEvent;
 
 @LargeTest
 public class FilesActivityUiTest extends ActivityTest<FilesActivity> {
@@ -115,4 +116,37 @@
         bot.waitForDeleteSnackbarGone();
         assertFalse(bot.hasDocuments("poodles.text"));
     }
+
+    // Tests that pressing tab switches focus between the roots and directory listings.
+    public void testKeyboard_tab() throws Exception {
+        bot.pressKey(KeyEvent.KEYCODE_TAB);
+        bot.assertHasFocus("com.android.documentsui:id/roots_list");
+        bot.pressKey(KeyEvent.KEYCODE_TAB);
+        bot.assertHasFocus("com.android.documentsui:id/dir_list");
+    }
+
+    // Tests that arrow keys do not switch focus away from the dir list.
+    public void testKeyboard_arrowsDirList() throws Exception {
+        for (int i = 0; i < 10; i++) {
+            bot.pressKey(KeyEvent.KEYCODE_DPAD_LEFT);
+            bot.assertHasFocus("com.android.documentsui:id/dir_list");
+        }
+        for (int i = 0; i < 10; i++) {
+            bot.pressKey(KeyEvent.KEYCODE_DPAD_RIGHT);
+            bot.assertHasFocus("com.android.documentsui:id/dir_list");
+        }
+    }
+
+    // Tests that arrow keys do not switch focus away from the roots list.
+    public void testKeyboard_arrowsRootsList() throws Exception {
+        bot.pressKey(KeyEvent.KEYCODE_TAB);
+        for (int i = 0; i < 10; i++) {
+            bot.pressKey(KeyEvent.KEYCODE_DPAD_RIGHT);
+            bot.assertHasFocus("com.android.documentsui:id/roots_list");
+        }
+        for (int i = 0; i < 10; i++) {
+            bot.pressKey(KeyEvent.KEYCODE_DPAD_LEFT);
+            bot.assertHasFocus("com.android.documentsui:id/roots_list");
+        }
+    }
 }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
index 4534c40..d2f8403 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
@@ -71,7 +71,7 @@
     UiObject findRoot(String label) throws UiObjectNotFoundException {
         final UiSelector rootsList = new UiSelector().resourceId(
                 "com.android.documentsui:id/container_roots").childSelector(
-                new UiSelector().resourceId("android:id/list"));
+                new UiSelector().resourceId("com.android.documentsui:id/roots_list"));
 
         // We might need to expand drawer if not visible
         if (!new UiObject(rootsList).waitForExists(mTimeout)) {
@@ -195,6 +195,15 @@
         assertNotNull(getSnackbar(mContext.getString(id)));
     }
 
+    /**
+     * Asserts that the specified view or one of its descendents has focus.
+     */
+    void assertHasFocus(String resourceName) {
+        UiObject2 candidate = mDevice.findObject(By.res(resourceName));
+        assertNotNull("Expected " + resourceName + " to have focus, but it didn't.",
+            candidate.findObject(By.focused(true)));
+    }
+
     void openDocument(String label) throws UiObjectNotFoundException {
         int toolType = Configurator.getInstance().getToolType();
         Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_FINGER);
@@ -309,7 +318,7 @@
     UiObject findDocument(String label) throws UiObjectNotFoundException {
         final UiSelector docList = new UiSelector().resourceId(
                 "com.android.documentsui:id/container_directory").childSelector(
-                        new UiSelector().resourceId("com.android.documentsui:id/list"));
+                        new UiSelector().resourceId("com.android.documentsui:id/dir_list"));
 
         // Wait for the first list item to appear
         new UiObject(docList.childSelector(new UiSelector())).waitForExists(mTimeout);
@@ -330,7 +339,7 @@
     UiObject findDocumentsList() {
         return findObject(
                 "com.android.documentsui:id/container_directory",
-                "com.android.documentsui:id/list");
+                "com.android.documentsui:id/dir_list");
     }
 
     UiObject findSearchView() {
@@ -416,4 +425,8 @@
         mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), mTimeout);
         mDevice.waitForIdle();
     }
+
+    void pressKey(int keyCode) {
+        mDevice.pressKeyCode(keyCode);
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerAdapter.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerAdapter.java
index 73171c7..1d6197a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerAdapter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerAdapter.java
@@ -65,7 +65,7 @@
     }
 
     public Tile getTile(int position) {
-        return mItems.get(position).tile;
+        return mItems.get(position) != null ? mItems.get(position).tile : null;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 8dec587..218e529 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -2546,6 +2546,7 @@
                             Slog.v(TAG, "Pushing back running sync due to a higher priority sync");
                         }
                         deferActiveSyncH(asc);
+                        break;
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index da62a2d..94b3b2d 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -55,8 +55,6 @@
 public class OtaDexoptService extends IOtaDexopt.Stub {
     private final static String TAG = "OTADexopt";
     private final static boolean DEBUG_DEXOPT = true;
-    // Apps used in the last 7 days.
-    private final static long DEXOPT_LRU_THRESHOLD_IN_MINUTES = 7 * 24 * 60;
 
     private final Context mContext;
     private final PackageDexOptimizer mPackageDexOptimizer;
@@ -94,69 +92,9 @@
         if (mDexoptPackages != null) {
             throw new IllegalStateException("already called prepare()");
         }
-
-        mDexoptPackages = new LinkedList<>();
-
-        ArrayList<PackageParser.Package> pkgs;
         synchronized (mPackageManagerService.mPackages) {
-            pkgs = new ArrayList<PackageParser.Package>(mPackageManagerService.mPackages.values());
-        }
-
-        // Sort apps by importance for dexopt ordering. Important apps are given more priority
-        // in case the device runs out of space.
-
-        // Give priority to core apps.
-        for (PackageParser.Package pkg : pkgs) {
-            if (pkg.coreApp) {
-                if (DEBUG_DEXOPT) {
-                    Log.i(TAG, "Adding core app " + mDexoptPackages.size() + ": " + pkg.packageName);
-                }
-                mDexoptPackages.add(pkg);
-            }
-        }
-        pkgs.removeAll(mDexoptPackages);
-
-        // Give priority to system apps that listen for pre boot complete.
-        Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
-        ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM);
-        for (PackageParser.Package pkg : pkgs) {
-            if (pkgNames.contains(pkg.packageName)) {
-                if (DEBUG_DEXOPT) {
-                    Log.i(TAG, "Adding pre boot system app " + mDexoptPackages.size() + ": " +
-                            pkg.packageName);
-                }
-                mDexoptPackages.add(pkg);
-            }
-        }
-        pkgs.removeAll(mDexoptPackages);
-
-        // Filter out packages that aren't recently used, add all remaining apps.
-        // TODO: add a property to control this?
-        if (mPackageManagerService.isHistoricalPackageUsageAvailable()) {
-            filterRecentlyUsedApps(pkgs, DEXOPT_LRU_THRESHOLD_IN_MINUTES * 60 * 1000);
-        }
-        mDexoptPackages.addAll(pkgs);
-
-        // Now go ahead and also add the libraries required for these packages.
-        // TODO: Think about interleaving things.
-        Set<PackageParser.Package> dependencies = new HashSet<>();
-        for (PackageParser.Package p : mDexoptPackages) {
-            dependencies.addAll(mPackageManagerService.findSharedNonSystemLibraries(p));
-        }
-        if (!dependencies.isEmpty()) {
-            dependencies.removeAll(mDexoptPackages);
-        }
-        mDexoptPackages.addAll(dependencies);
-
-        if (DEBUG_DEXOPT) {
-            StringBuilder sb = new StringBuilder();
-            for (PackageParser.Package pkg : mDexoptPackages) {
-                if (sb.length() > 0) {
-                    sb.append(", ");
-                }
-                sb.append(pkg.packageName);
-            }
-            Log.i(TAG, "Packages to be optimized: " + sb.toString());
+            mDexoptPackages = PackageManagerServiceUtils.getPackagesForDexopt(
+                    mPackageManagerService.mPackages.values(), mPackageManagerService);
         }
     }
 
@@ -228,29 +166,6 @@
         return pkgNames;
     }
 
-    private void filterRecentlyUsedApps(Collection<PackageParser.Package> pkgs,
-            long dexOptLRUThresholdInMills) {
-        // Filter out packages that aren't recently used.
-        int total = pkgs.size();
-        int skipped = 0;
-        long now = System.currentTimeMillis();
-        for (Iterator<PackageParser.Package> i = pkgs.iterator(); i.hasNext();) {
-            PackageParser.Package pkg = i.next();
-            long then = pkg.mLastPackageUsageTimeInMills;
-            if (then + dexOptLRUThresholdInMills < now) {
-                if (DEBUG_DEXOPT) {
-                    Log.i(TAG, "Skipping dexopt of " + pkg.packageName + " last resumed: " +
-                          ((then == 0) ? "never" : new Date(then)));
-                }
-                i.remove();
-                skipped++;
-            }
-        }
-        if (DEBUG_DEXOPT) {
-            Log.i(TAG, "Skipped optimizing " + skipped + " of " + total);
-        }
-    }
-
     private static class OTADexoptPackageDexOptimizer extends
             PackageDexOptimizer.ForcedUpdatePackageDexOptimizer {
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 31d16cd..8a94ce5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -282,6 +282,7 @@
 import java.util.Date;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -6778,32 +6779,19 @@
         // Extract pacakges only if profile-guided compilation is enabled because
         // otherwise BackgroundDexOptService will not dexopt them later.
         if (mUseJitProfiles) {
-            ArraySet<String> pkgs = getOptimizablePackages();
-            if (pkgs != null) {
-                for (String pkg : pkgs) {
-                    performDexOpt(pkg, null /* instructionSet */, false /* useProfiles */,
-                            true /* extractOnly */, false /* force */);
+            List<PackageParser.Package> pkgs;
+            synchronized (mPackages) {
+                pkgs = PackageManagerServiceUtils.getPackagesForDexopt(mPackages.values(), this);
+            }
+            for (PackageParser.Package pkg : pkgs) {
+                if (PackageDexOptimizer.canOptimizePackage(pkg)) {
+                    performDexOpt(pkg.packageName, null /* instructionSet */,
+                             false /* useProfiles */, true /* extractOnly */, false /* force */);
                 }
             }
         }
     }
 
-    private ArraySet<String> getPackageNamesForIntent(Intent intent, int userId) {
-        List<ResolveInfo> ris = null;
-        try {
-            ris = AppGlobals.getPackageManager().queryIntentReceivers(
-                    intent, null, 0, userId);
-        } catch (RemoteException e) {
-        }
-        ArraySet<String> pkgNames = new ArraySet<String>();
-        if (ris != null) {
-            for (ResolveInfo ri : ris) {
-                pkgNames.add(ri.activityInfo.packageName);
-            }
-        }
-        return pkgNames;
-    }
-
     @Override
     public void notifyPackageUse(String packageName) {
         synchronized (mPackages) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
new file mode 100644
index 0000000..a3ac514
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2016 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.pm;
+
+import android.app.AppGlobals;
+import android.content.Intent;
+import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.Package;
+import android.content.pm.ResolveInfo;
+import android.os.UserHandle;
+import android.os.RemoteException;
+import android.util.ArraySet;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
+import static com.android.server.pm.PackageManagerService.TAG;
+
+/**
+ * Class containing helper methods for the PackageManagerService.
+ *
+ * {@hide}
+ */
+public class PackageManagerServiceUtils {
+    private final static long SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000;
+
+    private static ArraySet<String> getPackageNamesForIntent(Intent intent, int userId) {
+        List<ResolveInfo> ris = null;
+        try {
+            ris = AppGlobals.getPackageManager().queryIntentReceivers(intent, null, 0, userId);
+        } catch (RemoteException e) {
+        }
+        ArraySet<String> pkgNames = new ArraySet<String>();
+        if (ris != null) {
+            for (ResolveInfo ri : ris) {
+                pkgNames.add(ri.activityInfo.packageName);
+            }
+        }
+        return pkgNames;
+    }
+
+    private static void filterRecentlyUsedApps(Collection<PackageParser.Package> pkgs,
+            long dexOptLRUThresholdInMills) {
+        // Filter out packages that aren't recently used.
+        int total = pkgs.size();
+        int skipped = 0;
+        long now = System.currentTimeMillis();
+        for (Iterator<PackageParser.Package> i = pkgs.iterator(); i.hasNext();) {
+            PackageParser.Package pkg = i.next();
+            long then = pkg.mLastPackageUsageTimeInMills;
+            if (then + dexOptLRUThresholdInMills < now) {
+                if (DEBUG_DEXOPT) {
+                    Log.i(TAG, "Skipping dexopt of " + pkg.packageName + " last resumed: " +
+                          ((then == 0) ? "never" : new Date(then)));
+                }
+                i.remove();
+                skipped++;
+            }
+        }
+        if (DEBUG_DEXOPT) {
+            Log.i(TAG, "Skipped dexopt " + skipped + " of " + total);
+        }
+    }
+
+    // Sort apps by importance for dexopt ordering. Important apps are given
+    // more priority in case the device runs out of space.
+    public static List<PackageParser.Package> getPackagesForDexopt(
+            Collection<PackageParser.Package> packages,
+            PackageManagerService packageManagerService) {
+        ArrayList<PackageParser.Package> remainingPkgs = new ArrayList<>(packages);
+        LinkedList<PackageParser.Package> result = new LinkedList<>();
+
+        // Give priority to core apps.
+        for (PackageParser.Package pkg : remainingPkgs) {
+            if (pkg.coreApp) {
+                if (DEBUG_DEXOPT) {
+                    Log.i(TAG, "Adding core app " + result.size() + ": " + pkg.packageName);
+                }
+                result.add(pkg);
+            }
+        }
+        remainingPkgs.removeAll(result);
+
+        // Give priority to system apps that listen for pre boot complete.
+        Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
+        ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM);
+        for (PackageParser.Package pkg : remainingPkgs) {
+            if (pkgNames.contains(pkg.packageName)) {
+                if (DEBUG_DEXOPT) {
+                    Log.i(TAG, "Adding pre boot system app " + result.size() + ": " +
+                            pkg.packageName);
+                }
+                result.add(pkg);
+            }
+        }
+        remainingPkgs.removeAll(result);
+
+        // Filter out packages that aren't recently used, add all remaining apps.
+        // TODO: add a property to control this?
+        if (packageManagerService.isHistoricalPackageUsageAvailable()) {
+            filterRecentlyUsedApps(remainingPkgs, SEVEN_DAYS_IN_MILLISECONDS);
+        }
+        result.addAll(remainingPkgs);
+
+        // Now go ahead and also add the libraries required for these packages.
+        // TODO: Think about interleaving things.
+        Set<PackageParser.Package> dependencies = new HashSet<>();
+        for (PackageParser.Package p : result) {
+            dependencies.addAll(packageManagerService.findSharedNonSystemLibraries(p));
+        }
+        if (!dependencies.isEmpty()) {
+            // We might have packages already in `result` that are dependencies
+            // of other packages. Make sure we don't add those to the list twice.
+            dependencies.removeAll(result);
+        }
+        result.addAll(dependencies);
+
+        if (DEBUG_DEXOPT) {
+            StringBuilder sb = new StringBuilder();
+            for (PackageParser.Package pkg : result) {
+                if (sb.length() > 0) {
+                    sb.append(", ");
+                }
+                sb.append(pkg.packageName);
+            }
+            Log.i(TAG, "Packages to be dexopted: " + sb.toString());
+        }
+
+        return result;
+    }
+}
\ No newline at end of file
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index 027c325..4fe97c1 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -61,7 +61,6 @@
  * @hide
  */
 public class IpManager extends StateMachine {
-    private static final String TAG = IpManager.class.getSimpleName();
     private static final boolean DBG = true;
     private static final boolean VDBG = false;
 
@@ -130,6 +129,7 @@
     private final State mStoppingState = new StoppingState();
     private final State mStartedState = new StartedState();
 
+    private final String mTag;
     private final Context mContext;
     private final String mInterfaceName;
     @VisibleForTesting
@@ -156,11 +156,11 @@
 
     public IpManager(Context context, String ifName, Callback callback)
                 throws IllegalArgumentException {
-        super(TAG + "." + ifName);
+        super(IpManager.class.getSimpleName() + "." + ifName);
+        mTag = getName();
 
         mContext = context;
         mInterfaceName = ifName;
-
         mCallback = callback;
 
         mNwService = INetworkManagementService.Stub.asInterface(
@@ -177,7 +177,7 @@
         try {
             mNwService.registerObserver(mNetlinkTracker);
         } catch (RemoteException e) {
-            Log.e(TAG, "Couldn't register NetlinkTracker: " + e.toString());
+            Log.e(mTag, "Couldn't register NetlinkTracker: " + e.toString());
         }
 
         resetLinkProperties();
@@ -200,7 +200,9 @@
      */
     @VisibleForTesting
     protected IpManager(String ifName, Callback callback) {
-        super(TAG + ".test-" + ifName);
+        super(IpManager.class.getSimpleName() + ".test-" + ifName);
+        mTag = getName();
+
         mInterfaceName = ifName;
         mCallback = callback;
 
@@ -273,7 +275,7 @@
                 mInterfaceName, mInterfaceIndex,
                 msg.arg1, msg.arg2, Objects.toString(msg.obj));
         if (VDBG) {
-            Log.d(TAG, getWhatToString(msg.what) + " " + logLine);
+            Log.d(mTag, getWhatToString(msg.what) + " " + logLine);
         }
         return logLine;
     }
@@ -283,7 +285,7 @@
             mInterfaceIndex = NetworkInterface.getByName(mInterfaceName).getIndex();
         } catch (SocketException | NullPointerException e) {
             // TODO: throw new IllegalStateException.
-            Log.e(TAG, "ALERT: Failed to get interface index: ", e);
+            Log.e(mTag, "ALERT: Failed to get interface index: ", e);
         }
     }
 
@@ -365,17 +367,17 @@
     private void dispatchCallback(ProvisioningChange delta, LinkProperties newLp) {
         switch (delta) {
             case GAINED_PROVISIONING:
-                if (VDBG) { Log.d(TAG, "onProvisioningSuccess()"); }
+                if (VDBG) { Log.d(mTag, "onProvisioningSuccess()"); }
                 mCallback.onProvisioningSuccess(newLp);
                 break;
 
             case LOST_PROVISIONING:
-                if (VDBG) { Log.d(TAG, "onProvisioningFailure()"); }
+                if (VDBG) { Log.d(mTag, "onProvisioningFailure()"); }
                 mCallback.onProvisioningFailure(newLp);
                 break;
 
             default:
-                if (VDBG) { Log.d(TAG, "onLinkPropertiesChange()"); }
+                if (VDBG) { Log.d(mTag, "onLinkPropertiesChange()"); }
                 mCallback.onLinkPropertiesChange(newLp);
                 break;
         }
@@ -396,7 +398,7 @@
             switch (delta) {
                 case GAINED_PROVISIONING:
                 case LOST_PROVISIONING:
-                    Log.d(TAG, "provisioning: " + delta);
+                    Log.d(mTag, "provisioning: " + delta);
                     break;
             }
         }
@@ -452,7 +454,7 @@
         }
 
         if (VDBG) {
-            Log.d(TAG, "newLp{" + newLp + "}");
+            Log.d(mTag, "newLp{" + newLp + "}");
         }
 
         return newLp;
@@ -464,7 +466,7 @@
             ifcg.setLinkAddress(new LinkAddress("0.0.0.0/0"));
             mNwService.setInterfaceConfig(mInterfaceName, ifcg);
         } catch (RemoteException e) {
-            Log.e(TAG, "ALERT: Failed to clear IPv4 address on interface " + mInterfaceName, e);
+            Log.e(mTag, "ALERT: Failed to clear IPv4 address on interface " + mInterfaceName, e);
         }
     }
 
@@ -474,7 +476,7 @@
         final ProvisioningChange delta = setLinkProperties(newLp);
 
         if (VDBG) {
-            Log.d(TAG, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")");
+            Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")");
         }
         mCallback.onNewDhcpResults(dhcpResults);
 
@@ -502,7 +504,7 @@
             delta = ProvisioningChange.LOST_PROVISIONING;
         }
 
-        if (VDBG) { Log.d(TAG, "onNewDhcpResults(null)"); }
+        if (VDBG) { Log.d(mTag, "onNewDhcpResults(null)"); }
         mCallback.onNewDhcpResults(null);
 
         dispatchCallback(delta, newLp);
@@ -518,7 +520,7 @@
                 mNwService.disableIpv6(mInterfaceName);
                 mNwService.clearInterfaceAddresses(mInterfaceName);
             } catch (Exception e) {
-                Log.e(TAG, "Failed to clear addresses or disable IPv6" + e);
+                Log.e(mTag, "Failed to clear addresses or disable IPv6" + e);
             }
 
             resetLinkProperties();
@@ -541,7 +543,7 @@
 
                 case DhcpStateMachine.CMD_ON_QUIT:
                     // Everything is already stopped.
-                    Log.e(TAG, "Unexpected CMD_ON_QUIT (already stopped).");
+                    Log.e(mTag, "Unexpected CMD_ON_QUIT (already stopped).");
                     break;
 
                 default:
@@ -584,9 +586,9 @@
                 mNwService.enableIpv6(mInterfaceName);
                 // TODO: Perhaps clearIPv4Address() as well.
             } catch (RemoteException re) {
-                Log.e(TAG, "Unable to change interface settings: " + re);
+                Log.e(mTag, "Unable to change interface settings: " + re);
             } catch (IllegalStateException ie) {
-                Log.e(TAG, "Unable to change interface settings: " + ie);
+                Log.e(mTag, "Unable to change interface settings: " + ie);
             }
 
             mIpReachabilityMonitor = new IpReachabilityMonitor(
@@ -607,7 +609,7 @@
                 if (applyStaticIpConfig()) {
                     handleIPv4Success(new DhcpResults(mStaticIpConfig));
                 } else {
-                    if (VDBG) { Log.d(TAG, "onProvisioningFailure()"); }
+                    if (VDBG) { Log.d(mTag, "onProvisioningFailure()"); }
                     mCallback.onProvisioningFailure(getLinkProperties());
                     transitionTo(mStoppingState);
                 }
@@ -640,7 +642,7 @@
                     break;
 
                 case CMD_START:
-                    Log.e(TAG, "ALERT: START received in StartedState. Please fix caller.");
+                    Log.e(mTag, "ALERT: START received in StartedState. Please fix caller.");
                     break;
 
                 case CMD_CONFIRM:
@@ -677,7 +679,7 @@
                 }
 
                 case DhcpStateMachine.CMD_PRE_DHCP_ACTION:
-                    if (VDBG) { Log.d(TAG, "onPreDhcpAction()"); }
+                    if (VDBG) { Log.d(mTag, "onPreDhcpAction()"); }
                     mCallback.onPreDhcpAction();
                     break;
 
@@ -685,7 +687,7 @@
                     // Note that onPostDhcpAction() is likely to be
                     // asynchronous, and thus there is no guarantee that we
                     // will be able to observe any of its effects here.
-                    if (VDBG) { Log.d(TAG, "onPostDhcpAction()"); }
+                    if (VDBG) { Log.d(mTag, "onPostDhcpAction()"); }
                     mCallback.onPostDhcpAction();
 
                     final DhcpResults dhcpResults = (DhcpResults) msg.obj;
@@ -697,14 +699,14 @@
                             handleIPv4Failure();
                             break;
                         default:
-                            Log.e(TAG, "Unknown CMD_POST_DHCP_ACTION status:" + msg.arg1);
+                            Log.e(mTag, "Unknown CMD_POST_DHCP_ACTION status:" + msg.arg1);
                     }
                     break;
                 }
 
                 case DhcpStateMachine.CMD_ON_QUIT:
                     // DHCPv4 quit early for some reason.
-                    Log.e(TAG, "Unexpected CMD_ON_QUIT.");
+                    Log.e(mTag, "Unexpected CMD_ON_QUIT.");
                     mDhcpStateMachine = null;
                     break;
 
@@ -720,9 +722,9 @@
             ifcg.setInterfaceUp();
             try {
                 mNwService.setInterfaceConfig(mInterfaceName, ifcg);
-                if (DBG) Log.d(TAG, "Static IP configuration succeeded");
+                if (DBG) Log.d(mTag, "Static IP configuration succeeded");
             } catch (IllegalStateException | RemoteException e) {
-                Log.e(TAG, "Static IP configuration failed: ", e);
+                Log.e(mTag, "Static IP configuration failed: ", e);
                 return false;
             }
 
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 2ad3c2e..d45ba03 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -892,6 +892,24 @@
     }
 
     /**
+     * Sets whether or not the given network is metered from a network policy
+     * point of view. A network should be classified as metered when the user is
+     * sensitive to heavy data usage on that connection due to monetary costs,
+     * data limitations or battery/performance issues. A typical example would
+     * be a wifi connection where the user was being charged for usage.
+     * @param netId the integer that identifies the network configuration
+     * to the supplicant.
+     * @param isMetered True to mark the network as metered.
+     * @return {@code true} if the operation succeeded.
+     * @hide
+     */
+    @SystemApi
+    public boolean setMetered(int netId, boolean isMetered) {
+        // TODO(jjoslin): Implement
+        return false;
+    }
+
+    /**
      * Remove the specified network from the list of configured networks.
      * This may result in the asynchronous delivery of state change
      * events.