Merge "Add com.android.development to the package whitelist." into pi-dev
diff --git a/api/current.txt b/api/current.txt
index 8a64dd0..83092f6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -43376,6 +43376,7 @@
   public class PrecomputedText implements android.text.Spannable {
     method public char charAt(int);
     method public static android.text.PrecomputedText create(java.lang.CharSequence, android.text.PrecomputedText.Params);
+    method public void getBounds(int, int, android.graphics.Rect);
     method public int getParagraphCount();
     method public int getParagraphEnd(int);
     method public int getParagraphStart(int);
@@ -43384,7 +43385,7 @@
     method public int getSpanFlags(java.lang.Object);
     method public int getSpanStart(java.lang.Object);
     method public <T> T[] getSpans(int, int, java.lang.Class<T>);
-    method public java.lang.CharSequence getText();
+    method public float getWidth(int, int);
     method public int length();
     method public int nextSpanTransition(int, int, java.lang.Class);
     method public void removeSpan(java.lang.Object);
diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt
index 90308d7..9c7bfb2 100644
--- a/config/hiddenapi-light-greylist.txt
+++ b/config/hiddenapi-light-greylist.txt
@@ -296,6 +296,7 @@
 Landroid/app/job/IJobScheduler$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
 Landroid/app/LoadedApk;->getAssets()Landroid/content/res/AssetManager;
 Landroid/app/LoadedApk;->getClassLoader()Ljava/lang/ClassLoader;
+Landroid/app/LoadedApk;->getCompatibilityInfo()Landroid/content/res/CompatibilityInfo;
 Landroid/app/LoadedApk;->getDataDirFile()Ljava/io/File;
 Landroid/app/LoadedApk;->getResources()Landroid/content/res/Resources;
 Landroid/app/LoadedApk;->mActivityThread:Landroid/app/ActivityThread;
@@ -377,8 +378,10 @@
 Landroid/app/Vr2dDisplayProperties$Builder;->setEnabled(Z)Landroid/app/Vr2dDisplayProperties$Builder;
 Landroid/app/Vr2dDisplayProperties;-><init>(III)V
 Landroid/app/VrManager;->getPersistentVrModeEnabled()Z
+Landroid/app/VrManager;->mService:Landroid/service/vr/IVrManager;
 Landroid/app/VrManager;->registerVrStateCallback(Landroid/app/VrStateCallback;Landroid/os/Handler;)V
 Landroid/app/VrManager;->setVr2dDisplayProperties(Landroid/app/Vr2dDisplayProperties;)V
+Landroid/app/VrManager;->unregisterVrStateCallback(Landroid/app/VrStateCallback;)V
 Landroid/app/WallpaperColors;->getColorHints()I
 Landroid/app/WallpaperManager;->getBitmap()Landroid/graphics/Bitmap;
 Landroid/app/WallpaperManager;->getBitmap(Z)Landroid/graphics/Bitmap;
@@ -447,6 +450,7 @@
 Landroid/content/ContentResolver;->unstableProviderDied(Landroid/content/IContentProvider;)V
 Landroid/content/ContentValues;-><init>(Ljava/util/HashMap;)V
 Landroid/content/ContentValues;->mValues:Ljava/util/HashMap;
+Landroid/content/Context;->getBasePackageName()Ljava/lang/String;
 Landroid/content/Context;->getSharedPrefsFile(Ljava/lang/String;)Ljava/io/File;
 Landroid/content/Context;->getThemeResId()I
 Landroid/content/Context;->sendBroadcastAsUser(Landroid/content/Intent;Landroid/os/UserHandle;Ljava/lang/String;I)V
@@ -469,6 +473,7 @@
 Landroid/content/Intent;->mExtras:Landroid/os/Bundle;
 Landroid/content/Intent;->putExtra(Ljava/lang/String;Landroid/os/IBinder;)Landroid/content/Intent;
 Landroid/content/pm/ActivityInfo;->resizeMode:I
+Landroid/content/pm/ActivityInfo;->supportsPictureInPicture()Z
 Landroid/content/pm/ApplicationInfo;->enabledSetting:I
 Landroid/content/pm/ApplicationInfo;->getBaseResourcePath()Ljava/lang/String;
 Landroid/content/pm/ApplicationInfo;->installLocation:I
@@ -2099,6 +2104,8 @@
 Landroid/util/SparseIntArray;->mKeys:[I
 Landroid/util/SparseIntArray;->mSize:I
 Landroid/util/SparseIntArray;->mValues:[I
+Landroid/view/accessibility/AccessibilityInteractionClient;->clearCache()V
+Landroid/view/accessibility/AccessibilityInteractionClient;->getInstance()Landroid/view/accessibility/AccessibilityInteractionClient;
 Landroid/view/accessibility/AccessibilityManager;->getInstance(Landroid/content/Context;)Landroid/view/accessibility/AccessibilityManager;
 Landroid/view/accessibility/AccessibilityManager;->isHighTextContrastEnabled()Z
 Landroid/view/accessibility/AccessibilityManager;->mAccessibilityStateChangeListeners:Landroid/util/ArrayMap;
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 4124536..7ebe0f9 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -43,6 +43,9 @@
 import android.util.Log;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.RejectedExecutionException;
@@ -924,6 +927,37 @@
                     idCount++;
                 }
             }
+
+            // The sort logic must match the logic in
+            // libcameraservice/common/CameraProviderManager.cpp::getAPI1CompatibleCameraDeviceIds
+            Arrays.sort(cameraIds, new Comparator<String>() {
+                    @Override
+                    public int compare(String s1, String s2) {
+                        int s1Int = 0, s2Int = 0;
+                        try {
+                            s1Int = Integer.parseInt(s1);
+                        } catch (NumberFormatException e) {
+                            s1Int = -1;
+                        }
+
+                        try {
+                            s2Int = Integer.parseInt(s2);
+                        } catch (NumberFormatException e) {
+                            s2Int = -1;
+                        }
+
+                        // Uint device IDs first
+                        if (s1Int >= 0 && s2Int >= 0) {
+                            return s1Int - s2Int;
+                        } else if (s1Int >= 0) {
+                            return -1;
+                        } else if (s2Int >= 0) {
+                            return 1;
+                        } else {
+                            // Simple string compare if both id are not uint
+                            return s1.compareTo(s2);
+                        }
+                    }});
             return cameraIds;
         }
 
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 2252571..411a97e 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -2105,8 +2105,8 @@
      * the thumbnail data will also be rotated.</p>
      * <p>Note that this orientation is relative to the orientation of the camera sensor, given
      * by {@link CameraCharacteristics#SENSOR_ORIENTATION android.sensor.orientation}.</p>
-     * <p>To translate from the device orientation given by the Android sensor APIs, the following
-     * sample code may be used:</p>
+     * <p>To translate from the device orientation given by the Android sensor APIs for camera
+     * sensors which are not EXTERNAL, the following sample code may be used:</p>
      * <pre><code>private int getJpegOrientation(CameraCharacteristics c, int deviceOrientation) {
      *     if (deviceOrientation == android.view.OrientationEventListener.ORIENTATION_UNKNOWN) return 0;
      *     int sensorOrientation = c.get(CameraCharacteristics.SENSOR_ORIENTATION);
@@ -2125,6 +2125,8 @@
      *     return jpegOrientation;
      * }
      * </code></pre>
+     * <p>For EXTERNAL cameras the sensor orientation will always be set to 0 and the facing will
+     * also be set to EXTERNAL. The above code is not relevant in such case.</p>
      * <p><b>Units</b>: Degrees in multiples of 90</p>
      * <p><b>Range of valid values:</b><br>
      * 0, 90, 180, 270</p>
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 8df5447..c156616 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2422,8 +2422,8 @@
      * the thumbnail data will also be rotated.</p>
      * <p>Note that this orientation is relative to the orientation of the camera sensor, given
      * by {@link CameraCharacteristics#SENSOR_ORIENTATION android.sensor.orientation}.</p>
-     * <p>To translate from the device orientation given by the Android sensor APIs, the following
-     * sample code may be used:</p>
+     * <p>To translate from the device orientation given by the Android sensor APIs for camera
+     * sensors which are not EXTERNAL, the following sample code may be used:</p>
      * <pre><code>private int getJpegOrientation(CameraCharacteristics c, int deviceOrientation) {
      *     if (deviceOrientation == android.view.OrientationEventListener.ORIENTATION_UNKNOWN) return 0;
      *     int sensorOrientation = c.get(CameraCharacteristics.SENSOR_ORIENTATION);
@@ -2442,6 +2442,8 @@
      *     return jpegOrientation;
      * }
      * </code></pre>
+     * <p>For EXTERNAL cameras the sensor orientation will always be set to 0 and the facing will
+     * also be set to EXTERNAL. The above code is not relevant in such case.</p>
      * <p><b>Units</b>: Degrees in multiples of 90</p>
      * <p><b>Range of valid values:</b><br>
      * 0, 90, 180, 270</p>
diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java
index 413df05..44789d6 100644
--- a/core/java/android/text/PrecomputedText.java
+++ b/core/java/android/text/PrecomputedText.java
@@ -16,6 +16,7 @@
 
 package android.text;
 
+import android.annotation.FloatRange;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -44,13 +45,17 @@
  * <pre>
  * An example usage is:
  * <code>
- *  void asyncSetText(final TextView textView, final String longString, Handler bgThreadHandler) {
+ *  static void asyncSetText(TextView textView, final String longString, Executor bgExecutor) {
  *      // construct precompute related parameters using the TextView that we will set the text on.
- *      final PrecomputedText.Params params = textView.getTextParams();
- *      bgThreadHandler.post(() -> {
- *          final PrecomputedText precomputedText =
- *                  PrecomputedText.create(expensiveLongString, params);
+ *      final PrecomputedText.Params params = textView.getTextMetricsParams();
+ *      final Reference textViewRef = new WeakReference<>(textView);
+ *      bgExecutor.submit(() -> {
+ *          TextView textView = textViewRef.get();
+ *          if (textView == null) return;
+ *          final PrecomputedText precomputedText = PrecomputedText.create(longString, params);
  *          textView.post(() -> {
+ *              TextView textView = textViewRef.get();
+ *              if (textView == null) return;
  *              textView.setText(precomputedText);
  *          });
  *      });
@@ -363,6 +368,7 @@
 
     /**
      * Return the underlying text.
+     * @hide
      */
     public @NonNull CharSequence getText() {
         return mText;
@@ -451,27 +457,61 @@
             + ", gave " + pos);
     }
 
-    /** @hide */
-    public float getWidth(@IntRange(from = 0) int start, @IntRange(from = 0) int end) {
+    /**
+     * Returns text width for the given range.
+     * Both {@code start} and {@code end} offset need to be in the same paragraph, otherwise
+     * IllegalArgumentException will be thrown.
+     *
+     * @param start the inclusive start offset in the text
+     * @param end the exclusive end offset in the text
+     * @return the text width
+     * @throws IllegalArgumentException if start and end offset are in the different paragraph.
+     */
+    public @FloatRange(from = 0) float getWidth(@IntRange(from = 0) int start,
+            @IntRange(from = 0) int end) {
+        Preconditions.checkArgument(0 <= start && start <= mText.length(), "invalid start offset");
+        Preconditions.checkArgument(0 <= end && end <= mText.length(), "invalid end offset");
+        Preconditions.checkArgument(start <= end, "start offset can not be larger than end offset");
+
+        if (start == end) {
+            return 0;
+        }
         final int paraIndex = findParaIndex(start);
         final int paraStart = getParagraphStart(paraIndex);
         final int paraEnd = getParagraphEnd(paraIndex);
         if (start < paraStart || paraEnd < end) {
-            throw new RuntimeException("Cannot measured across the paragraph:"
+            throw new IllegalArgumentException("Cannot measured across the paragraph:"
                 + "para: (" + paraStart + ", " + paraEnd + "), "
                 + "request: (" + start + ", " + end + ")");
         }
         return getMeasuredParagraph(paraIndex).getWidth(start - paraStart, end - paraStart);
     }
 
-    /** @hide */
+    /**
+     * Retrieves the text bounding box for the given range.
+     * Both {@code start} and {@code end} offset need to be in the same paragraph, otherwise
+     * IllegalArgumentException will be thrown.
+     *
+     * @param start the inclusive start offset in the text
+     * @param end the exclusive end offset in the text
+     * @param bounds the output rectangle
+     * @throws IllegalArgumentException if start and end offset are in the different paragraph.
+     */
     public void getBounds(@IntRange(from = 0) int start, @IntRange(from = 0) int end,
             @NonNull Rect bounds) {
+        Preconditions.checkArgument(0 <= start && start <= mText.length(), "invalid start offset");
+        Preconditions.checkArgument(0 <= end && end <= mText.length(), "invalid end offset");
+        Preconditions.checkArgument(start <= end, "start offset can not be larger than end offset");
+        Preconditions.checkNotNull(bounds);
+        if (start == end) {
+            bounds.set(0, 0, 0, 0);
+            return;
+        }
         final int paraIndex = findParaIndex(start);
         final int paraStart = getParagraphStart(paraIndex);
         final int paraEnd = getParagraphEnd(paraIndex);
         if (start < paraStart || paraEnd < end) {
-            throw new RuntimeException("Cannot measured across the paragraph:"
+            throw new IllegalArgumentException("Cannot measured across the paragraph:"
                 + "para: (" + paraStart + ", " + paraEnd + "), "
                 + "request: (" + start + ", " + end + ")");
         }
diff --git a/core/res/res/drawable/ic_account_circle.xml b/core/res/res/drawable/ic_account_circle.xml
index a8c5b8c..f7317db 100644
--- a/core/res/res/drawable/ic_account_circle.xml
+++ b/core/res/res/drawable/ic_account_circle.xml
@@ -16,9 +16,16 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
         android:width="48.0dp"
         android:height="48.0dp"
-        android:viewportWidth="48.0"
-        android:viewportHeight="48.0">
-    <path
-        android:pathData="M24,0C10.8,0 0,10.8 0,24s10.8,24 24,24s24,-10.8 24,-24S37.200001,0 24,0zM24,7.2c3.96,0 7.2,3.24 7.2,7.2s-3.24,7.2 -7.2,7.2s-7.2,-3.24 -7.2,-7.2S20.040001,7.2 24,7.2zM24,41.279999c-6,0 -11.28,-3.12 -14.4,-7.68c0.12,-4.8 9.6,-7.44 14.4,-7.44s14.28,2.64 14.4,7.44C35.279999,38.16 30,41.279999 24,41.279999z"
-        android:fillColor="#FFFFFFFF"/>
+        android:viewportWidth="20.0"
+        android:viewportHeight="20.0">
+    <group
+        android:translateX="-2"
+        android:translateY="-2" >
+        <path
+            android:pathData="M12,2C6.48,2 2,6.48 2,12c0,5.52 4.48,10 10,10c5.52,0 10,-4.48 10,-10C22,6.48 17.52,2 12,2zM18.36,16.83c-1.43,-1.74 -4.9,-2.33 -6.36,-2.33s-4.93,0.59 -6.36,2.33C4.62,15.49 4,13.82 4,12c0,-4.41 3.59,-8 8,-8c4.41,0 8,3.59 8,8C20,13.82 19.38,15.49 18.36,16.83z"
+            android:fillColor="#FFFFFFFF" />
+        <path
+            android:pathData="M12,6c-1.94,0 -3.5,1.56 -3.5,3.5S10.06,13 12,13c1.94,0 3.5,-1.56 3.5,-3.5S13.94,6 12,6z"
+            android:fillColor="#FFFFFFFF" />
+    </group>
 </vector>
diff --git a/core/res/res/drawable/ic_lock_bugreport.xml b/core/res/res/drawable/ic_lock_bugreport.xml
index 5501254..ebeb532 100644
--- a/core/res/res/drawable/ic_lock_bugreport.xml
+++ b/core/res/res/drawable/ic_lock_bugreport.xml
@@ -21,5 +21,11 @@
         android:tint="?attr/colorControlNormal">
     <path
         android:fillColor="#FF000000"
-        android:pathData="M20.0,8.0l-2.81,0.0a5.985,5.985 0.0,0.0 0.0,-1.82 -1.96l0.93,-0.93a0.99,0.996 0.0,1.0 0.0,-1.41 -1.41l-1.47,1.47C12.96,5.06 12.49,5.0 12.0,5.0s-0.9,0.06 -1.4,0.17L9.12,3.7a0.99,0.996 0.0,1.0 0.0,-1.41 1.41l0.9,0.92C7.88,6.55 7.26,7.22 6.81,8.0L4.0,8.0c-0.55,0.0 -1.0,0.45 -1.0,1.0s0.45,1.0 1.0,1.0l2.09,0.0c0.0,0.33 0.0,0.66 -0.09,1.0l0.0,1.0L4.0,12.0c-0.55,0.0 -1.0,0.45 -1.0,1.0s0.45,1.0 1.0,1.0l2.0,0.0l0.0,1.0c0.0,0.3 0.0,0.6 0.09,1.0L4.0,16.0c-0.55,0.0 -1.0,0.45 -1.0,1.0s0.45,1.0 1.0,1.0l2.81,0.0c1.04,1.79 2.97,3.0 5.19,3.0s4.15,-1.21 5.19,-3.0L20.0,18.0c0.55,0.0 1.0,-0.45 1.0,-1.0s-0.45,-1.0 -1.0,-1.0l-2.09,0.0c0.05,-0.3 0.09,-0.6 0.09,-1.0l0.0,-1.0l2.0,0.0c0.55,0.0 1.0,-0.45 1.0,-1.0s-0.45,-1.0 -1.0,-1.0l-2.0,0.0l0.0,-1.0c0.0,-0.34 -0.04,-0.67 -0.09,-1.0L20.0,10.0c0.55,0.0 1.0,-0.45 1.0,-1.0s-0.45,-1.0 -1.0,-1.0zm-6.0,8.0l-4.0,0.0l0.0,-2.0l4.0,0.0l0.0,2.0zm0.0,-4.0l-4.0,0.0l0.0,-2.0l4.0,0.0l0.0,2.0z"/>
+        android:pathData="M20,10V8h-2.81c-0.45,-0.78 -1.07,-1.46 -1.82,-1.96L17,4.41L15.59,3l-2.17,2.17c-0.03,-0.01 -0.05,-0.01 -0.08,-0.01c-0.16,-0.04 -0.32,-0.06 -0.49,-0.09c-0.06,-0.01 -0.11,-0.02 -0.17,-0.03C12.46,5.02 12.23,5 12,5h0c-0.49,0 -0.97,0.07 -1.42,0.18l0.02,-0.01L8.41,3L7,4.41l1.62,1.63l0.01,0C7.88,6.54 7.26,7.22 6.81,8H4v2h2.09C6.03,10.33 6,10.66 6,11v1H4v2h2v1c0,0.34 0.04,0.67 0.09,1H4v2h2.81c1.04,1.79 2.97,3 5.19,3h0c2.22,0 4.15,-1.21 5.19,-3H20v-2h-2.09l0,0c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1l0,0H20zM16,15c0,2.21 -1.79,4 -4,4c-2.21,0 -4,-1.79 -4,-4v-4c0,-2.21 1.79,-4 4,-4h0c2.21,0 4,1.79 4,4V15z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M10,14h4v2h-4z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M10,10h4v2h-4z"/>
 </vector>
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 00dc22e..31abf92 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -59,23 +59,131 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
- *  Class for decoding images as {@link Bitmap}s or {@link Drawable}s.
+ *  <p>A class for converting encoded images (like {@code PNG}, {@code JPEG},
+ *  {@code WEBP}, {@code GIF}, or {@code HEIF}) into {@link Drawable} or
+ *  {@link Bitmap} objects.
+ *
+ *  <p>To use it, first create a {@link Source Source} using one of the
+ *  {@code createSource} overloads. For example, to decode from a {@link File}, call
+ *  {@link #createSource(File)} and pass the result to {@link #decodeDrawable(Source)}
+ *  or {@link #decodeBitmap(Source)}:
+ *
+ *  <pre class="prettyprint">
+ *  File file = new File(...);
+ *  ImageDecoder.Source source = ImageDecoder.createSource(file);
+ *  Drawable drawable = ImageDecoder.decodeDrawable(source);
+ *  </pre>
+ *
+ *  <p>To change the default settings, pass the {@link Source Source} and an
+ *  {@link OnHeaderDecodedListener OnHeaderDecodedListener} to
+ *  {@link #decodeDrawable(Source, OnHeaderDecodedListener)} or
+ *  {@link #decodeBitmap(Source, OnHeaderDecodedListener)}. For example, to
+ *  create a sampled image with half the width and height of the original image,
+ *  call {@link #setTargetSampleSize setTargetSampleSize(2)} inside
+ *  {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}:
+ *
+ *  <pre class="prettyprint">
+ *  OnHeaderDecodedListener listener = new OnHeaderDecodedListener() {
+ *      public void onHeaderDecoded(ImageDecoder decoder, ImageInfo info, Source source) {
+ *          decoder.setTargetSampleSize(2);
+ *      }
+ *  };
+ *  Drawable drawable = ImageDecoder.decodeDrawable(source, listener);
+ *  </pre>
+ *
+ *  <p>The {@link ImageInfo ImageInfo} contains information about the encoded image, like
+ *  its width and height, and the {@link Source Source} can be used to match to a particular
+ *  {@link Source Source} if a single {@link OnHeaderDecodedListener OnHeaderDecodedListener}
+ *  is used with multiple {@link Source Source} objects.
+ *
+ *  <p>The {@link OnHeaderDecodedListener OnHeaderDecodedListener} can also be implemented
+ *  as a lambda:
+ *
+ *  <pre class="prettyprint">
+ *  Drawable drawable = ImageDecoder.decodeDrawable(source, (decoder, info, src) -&gt; {
+ *      decoder.setTargetSampleSize(2);
+ *  });
+ *  </pre>
+ *
+ *  <p>If the encoded image is an animated {@code GIF} or {@code WEBP},
+ *  {@link #decodeDrawable decodeDrawable} will return an {@link AnimatedImageDrawable}. To
+ *  start its animation, call {@link AnimatedImageDrawable#start AnimatedImageDrawable.start()}:
+ *
+ *  <pre class="prettyprint">
+ *  Drawable drawable = ImageDecoder.decodeDrawable(source);
+ *  if (drawable instanceof AnimatedImageDrawable) {
+ *      ((AnimatedImageDrawable) drawable).start();
+ *  }
+ *  </pre>
+ *
+ *  <p>By default, a {@link Bitmap} created by {@link ImageDecoder} (including
+ *  one that is inside a {@link Drawable}) will be immutable (i.e.
+ *  {@link Bitmap#isMutable Bitmap.isMutable()} returns {@code false}), and it
+ *  will typically have {@code Config} {@link Bitmap.Config#HARDWARE}. Although
+ *  these properties can be changed with {@link #setMutableRequired setMutableRequired(true)}
+ *  (which is only compatible with {@link #decodeBitmap(Source)} and
+ *  {@link #decodeBitmap(Source, OnHeaderDecodedListener)}) and {@link #setAllocator},
+ *  it is also possible to apply custom effects regardless of the mutability of
+ *  the final returned object by passing a {@link PostProcessor} to
+ *  {@link #setPostProcessor setPostProcessor}. A {@link PostProcessor} can also be a lambda:
+ *
+ *  <pre class="prettyprint">
+ *  Drawable drawable = ImageDecoder.decodeDrawable(source, (decoder, info, src) -&gt; {
+ *      decoder.setPostProcessor((canvas) -&gt; {
+ *              // This will create rounded corners.
+ *              Path path = new Path();
+ *              path.setFillType(Path.FillType.INVERSE_EVEN_ODD);
+ *              int width = canvas.getWidth();
+ *              int height = canvas.getHeight();
+ *              path.addRoundRect(0, 0, width, height, 20, 20, Path.Direction.CW);
+ *              Paint paint = new Paint();
+ *              paint.setAntiAlias(true);
+ *              paint.setColor(Color.TRANSPARENT);
+ *              paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
+ *              canvas.drawPath(path, paint);
+ *              return PixelFormat.TRANSLUCENT;
+ *      });
+ *  });
+ *  </pre>
+ *
+ *  <p>If the encoded image is incomplete or contains an error, or if an
+ *  {@link Exception} occurs during decoding, a {@link DecodeException DecodeException}
+ *  will be thrown. In some cases, the {@link ImageDecoder} may have decoded part of
+ *  the image. In order to display the partial image, an
+ *  {@link OnPartialImageListener OnPartialImageListener} must be passed to
+ *  {@link #setOnPartialImageListener setOnPartialImageListener}. For example:
+ *
+ *  <pre class="prettyprint">
+ *  Drawable drawable = ImageDecoder.decodeDrawable(source, (decoder, info, src) -&gt; {
+ *      decoder.setOnPartialImageListener((DecodeException e) -&gt; {
+ *              // Returning true indicates to create a Drawable or Bitmap even
+ *              // if the whole image could not be decoded. Any remaining lines
+ *              // will be blank.
+ *              return true;
+ *      });
+ *  });
+ *  </pre>
  */
 public final class ImageDecoder implements AutoCloseable {
     /** @hide **/
     public static int sApiLevel;
 
     /**
-     *  Source of the encoded image data.
+     *  Source of encoded image data.
      *
-     *  <p>This object references the data that will be used to decode a
-     *  Drawable or Bitmap in {@link #decodeDrawable} or {@link #decodeBitmap}.
-     *  Constructing a {@code Source} (with one of the overloads of
-     *  {@code createSource}) can be done on any thread because the construction
-     *  simply captures values. The real work is done in decodeDrawable or
-     *  decodeBitmap.</p>
+     *  <p>References the data that will be used to decode a {@link Drawable}
+     *  or {@link Bitmap} in {@link #decodeDrawable decodeDrawable} or
+     *  {@link #decodeBitmap decodeBitmap}. Constructing a {@code Source} (with
+     *  one of the overloads of {@code createSource}) can be done on any thread
+     *  because the construction simply captures values. The real work is done
+     *  in {@link #decodeDrawable decodeDrawable} or {@link #decodeBitmap decodeBitmap}.
      *
-     *  <p>Further, a Source object can be reused with different settings, or
+     *  <p>A {@code Source} object can be reused to create multiple versions of the
+     *  same image. For example, to decode a full size image and its thumbnail,
+     *  the same {@code Source} can be used once with no
+     *  {@link OnHeaderDecodedListener OnHeaderDecodedListener} and once with an
+     *  implementation of {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}
+     *  that calls {@link #setTargetSize} with smaller dimensions. One {@code Source}
      *  even used simultaneously in multiple threads.</p>
      */
     public static abstract class Source {
@@ -418,7 +526,7 @@
     }
 
     /**
-     *  Contains information about the encoded image.
+     *  Information about an encoded image.
      */
     public static class ImageInfo {
         private final Size mSize;
@@ -448,8 +556,8 @@
         /**
          * Whether the image is animated.
          *
-         * <p>Calling {@link #decodeDrawable} will return an
-         * {@link AnimatedImageDrawable}.</p>
+         * <p>If {@code true}, {@link #decodeDrawable decodeDrawable} will
+         * return an {@link AnimatedImageDrawable}.</p>
          */
         public boolean isAnimated() {
             return mDecoder.mAnimated;
@@ -475,19 +583,25 @@
     public static class IncompleteException extends IOException {};
 
     /**
-     *  Optional listener supplied to {@link #decodeDrawable} or
-     *  {@link #decodeBitmap}.
+     *  Interface for changing the default settings of a decode.
      *
-     *  <p>This is necessary in order to change the default settings of the
-     *  decode.</p>
+     *  <p>Supply an instance to
+     *  {@link #decodeDrawable(Source, OnHeaderDecodedListener) decodeDrawable}
+     *  or {@link #decodeBitmap(Source, OnHeaderDecodedListener) decodeBitmap},
+     *  which will call {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}
+     *  (in the same thread) once the size is known. The implementation of
+     *  {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded} can then
+     *  change the decode settings as desired.
      */
     public static interface OnHeaderDecodedListener {
         /**
-         *  Called when the header is decoded and the size is known.
+         *  Called by {@link ImageDecoder} when the header has been decoded and
+         *  the image size is known.
          *
-         *  @param decoder allows changing the default settings of the decode.
-         *  @param info Information about the encoded image.
-         *  @param source that created the decoder.
+         *  @param decoder the object performing the decode, for changing
+         *      its default settings.
+         *  @param info information about the encoded image.
+         *  @param source object that created {@code decoder}.
          */
         public void onHeaderDecoded(@NonNull ImageDecoder decoder,
                 @NonNull ImageInfo info, @NonNull Source source);
@@ -570,7 +684,7 @@
         }
 
         /**
-         *  Retrieve the {@link Source} that was interrupted.
+         *  Retrieve the {@link Source Source} that was interrupted.
          *
          *  <p>This can be used for equality checking to find the Source which
          *  failed to completely decode.</p>
@@ -595,25 +709,36 @@
     }
 
     /**
-     *  Optional listener supplied to the ImageDecoder.
+     *  Interface for inspecting a {@link DecodeException DecodeException}
+     *  and potentially preventing it from being thrown.
      *
-     *  Without this listener, errors will throw {@link java.io.IOException}.
+     *  <p>If an instance is passed to
+     *  {@link #setOnPartialImageListener setOnPartialImageListener}, a
+     *  {@link DecodeException DecodeException} that would otherwise have been
+     *  thrown can be inspected inside
+     *  {@link OnPartialImageListener#onPartialImage onPartialImage}.
+     *  If {@link OnPartialImageListener#onPartialImage onPartialImage} returns
+     *  {@code true}, a partial image will be created.
      */
     public static interface OnPartialImageListener {
         /**
-         *  Called when there is only a partial image to display.
+         *  Called by {@link ImageDecoder} when there is only a partial image to
+         *  display.
          *
-         *  If decoding is interrupted after having decoded a partial image,
-         *  this listener lets the client know that and allows them to
-         *  optionally finish the rest of the decode/creation process to create
-         *  a partial {@link Drawable}/{@link Bitmap}.
+         *  <p>If decoding is interrupted after having decoded a partial image,
+         *  this method will be called. The implementation can inspect the
+         *  {@link DecodeException DecodeException} and optionally finish the
+         *  rest of the decode creation process to create a partial {@link Drawable}
+         *  or {@link Bitmap}.
          *
-         *  @param e containing information about the decode interruption.
-         *  @return True to create and return a {@link Drawable}/{@link Bitmap}
-         *      with partial data. False (which is the default) to abort the
-         *      decode and throw {@code e}.
+         *  @param exception exception containing information about the
+         *      decode interruption.
+         *  @return {@code true} to create and return a {@link Drawable} or
+         *      {@link Bitmap} with partial data. {@code false} (which is the
+         *      default) to abort the decode and throw {@code e}. Any undecoded
+         *      lines in the image will be blank.
          */
-        boolean onPartialImage(@NonNull DecodeException e);
+        boolean onPartialImage(@NonNull DecodeException exception);
     };
 
     // Fields
@@ -679,12 +804,13 @@
     }
 
     /**
-     * Create a new {@link Source} from a resource.
+     * Create a new {@link Source Source} from a resource.
      *
      * @param res the {@link Resources} object containing the image data.
      * @param resId resource ID of the image data.
      * @return a new Source object, which can be passed to
-     *      {@link #decodeDrawable} or {@link #decodeBitmap}.
+     *      {@link #decodeDrawable decodeDrawable} or
+     *      {@link #decodeBitmap decodeBitmap}.
      */
     @AnyThread
     @NonNull
@@ -694,12 +820,20 @@
     }
 
     /**
-     * Create a new {@link Source} from a {@link android.net.Uri}.
+     * Create a new {@link Source Source} from a {@link android.net.Uri}.
+     *
+     * <h5>Accepts the following URI schemes:</h5>
+     * <ul>
+     * <li>content ({@link ContentResolver#SCHEME_CONTENT})</li>
+     * <li>android.resource ({@link ContentResolver#SCHEME_ANDROID_RESOURCE})</li>
+     * <li>file ({@link ContentResolver#SCHEME_FILE})</li>
+     * </ul>
      *
      * @param cr to retrieve from.
      * @param uri of the image file.
      * @return a new Source object, which can be passed to
-     *      {@link #decodeDrawable} or {@link #decodeBitmap}.
+     *      {@link #decodeDrawable decodeDrawable} or
+     *      {@link #decodeBitmap decodeBitmap}.
      */
     @AnyThread
     @NonNull
@@ -721,7 +855,7 @@
     }
 
     /**
-     * Create a new {@link Source} from a file in the "assets" directory.
+     * Create a new {@link Source Source} from a file in the "assets" directory.
      */
     @AnyThread
     @NonNull
@@ -730,12 +864,15 @@
     }
 
     /**
-     * Create a new {@link Source} from a byte array.
+     * Create a new {@link Source Source} from a byte array.
      *
      * @param data byte array of compressed image data.
      * @param offset offset into data for where the decoder should begin
      *      parsing.
      * @param length number of bytes, beginning at offset, to parse.
+     * @return a new Source object, which can be passed to
+     *      {@link #decodeDrawable decodeDrawable} or
+     *      {@link #decodeBitmap decodeBitmap}.
      * @throws NullPointerException if data is null.
      * @throws ArrayIndexOutOfBoundsException if offset and length are
      *      not within data.
@@ -767,16 +904,20 @@
     }
 
     /**
-     * Create a new {@link Source} from a {@link java.nio.ByteBuffer}.
+     * Create a new {@link Source Source} from a {@link java.nio.ByteBuffer}.
      *
-     * <p>Decoding will start from {@link java.nio.ByteBuffer#position()}. The
-     * position of {@code buffer} will not be affected.</p>
+     * <p>Decoding will start from {@link java.nio.ByteBuffer#position() buffer.position()}.
+     * The position of {@code buffer} will not be affected.</p>
      *
-     * <p>Note: If this {@code Source} is passed to {@link #decodeDrawable}, and
-     * the encoded image is animated, the returned {@link AnimatedImageDrawable}
+     * <p>Note: If this {@code Source} is passed to {@link #decodeDrawable decodeDrawable},
+     * and the encoded image is animated, the returned {@link AnimatedImageDrawable}
      * will continue reading from the {@code buffer}, so its contents must not
      * be modified, even after the {@code AnimatedImageDrawable} is returned.
      * {@code buffer}'s contents should never be modified during decode.</p>
+     *
+     * @return a new Source object, which can be passed to
+     *      {@link #decodeDrawable decodeDrawable} or
+     *      {@link #decodeBitmap decodeBitmap}.
      */
     @AnyThread
     @NonNull
@@ -812,7 +953,11 @@
     }
 
     /**
-     * Create a new {@link Source} from a {@link java.io.File}.
+     * Create a new {@link Source Source} from a {@link java.io.File}.
+     *
+     * @return a new Source object, which can be passed to
+     *      {@link #decodeDrawable decodeDrawable} or
+     *      {@link #decodeBitmap decodeBitmap}.
      */
     @AnyThread
     @NonNull
@@ -863,14 +1008,17 @@
      *  Specify the size of the output {@link Drawable} or {@link Bitmap}.
      *
      *  <p>By default, the output size will match the size of the encoded
-     *  image, which can be retrieved from the {@link ImageInfo} in
-     *  {@link OnHeaderDecodedListener#onHeaderDecoded}.</p>
+     *  image, which can be retrieved from the {@link ImageInfo ImageInfo} in
+     *  {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p>
+     *
+     *  <p>This will sample or scale the output to an arbitrary size that may
+     *  be smaller or larger than the encoded size.</p>
      *
      *  <p>Only the last call to this or {@link #setTargetSampleSize} is
      *  respected.</p>
      *
      *  <p>Like all setters on ImageDecoder, this must be called inside
-     *  {@link OnHeaderDecodedListener#onHeaderDecoded}.</p>
+     *  {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p>
      *
      *  @param width must be greater than 0.
      *  @param height must be greater than 0.
@@ -924,13 +1072,13 @@
      *  Set the target size with a sampleSize.
      *
      *  <p>By default, the output size will match the size of the encoded
-     *  image, which can be retrieved from the {@link ImageInfo} in
-     *  {@link OnHeaderDecodedListener#onHeaderDecoded}.</p>
+     *  image, which can be retrieved from the {@link ImageInfo ImageInfo} in
+     *  {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p>
      *
      *  <p>Requests the decoder to subsample the original image, returning a
-     *  smaller image to save memory. The sample size is the number of pixels
+     *  smaller image to save memory. The {@code sampleSize} is the number of pixels
      *  in either dimension that correspond to a single pixel in the output.
-     *  For example, sampleSize == 4 returns an image that is 1/4 the
+     *  For example, {@code sampleSize == 4} returns an image that is 1/4 the
      *  width/height of the original, and 1/16 the number of pixels.</p>
      *
      *  <p>Must be greater than or equal to 1.</p>
@@ -938,7 +1086,7 @@
      *  <p>Only the last call to this or {@link #setTargetSize} is respected.</p>
      *
      *  <p>Like all setters on ImageDecoder, this must be called inside
-     *  {@link OnHeaderDecodedListener#onHeaderDecoded}.</p>
+     *  {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p>
      *
      *  @param sampleSize Sampling rate of the encoded image.
      */
@@ -960,7 +1108,8 @@
      *  Will typically result in a {@link Bitmap.Config#HARDWARE}
      *  allocation, but may be software for small images. In addition, this will
      *  switch to software when HARDWARE is incompatible, e.g.
-     *  {@link #setMutableRequired}, {@link #setDecodeAsAlphaMaskEnabled}.
+     *  {@link #setMutableRequired setMutableRequired(true)} or
+     *  {@link #setDecodeAsAlphaMaskEnabled setDecodeAsAlphaMaskEnabled(true)}.
      */
     public static final int ALLOCATOR_DEFAULT = 0;
 
@@ -983,9 +1132,10 @@
      *  Require a {@link Bitmap.Config#HARDWARE} {@link Bitmap}.
      *
      *  When this is combined with incompatible options, like
-     *  {@link #setMutableRequired} or {@link #setDecodeAsAlphaMaskEnabled},
-     *  {@link #decodeDrawable} / {@link #decodeBitmap} will throw an
-     *  {@link java.lang.IllegalStateException}.
+     *  {@link #setMutableRequired setMutableRequired(true)} or
+     *  {@link #setDecodeAsAlphaMaskEnabled setDecodeAsAlphaMaskEnabled(true)},
+     *  {@link #decodeDrawable decodeDrawable} or {@link #decodeBitmap decodeBitmap}
+     *  will throw an {@link java.lang.IllegalStateException}.
      */
     public static final int ALLOCATOR_HARDWARE = 3;
 
@@ -1002,7 +1152,7 @@
      *  <p>This is ignored for animated drawables.</p>
      *
      *  <p>Like all setters on ImageDecoder, this must be called inside
-     *  {@link OnHeaderDecodedListener#onHeaderDecoded}.</p>
+     *  {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p>
      *
      *  @param allocator Type of allocator to use.
      */
@@ -1029,13 +1179,13 @@
      *  {@link android.view.View} system (i.e. to a {@link Canvas}). Calling
      *  this method with a value of {@code true} will result in
      *  {@link #decodeBitmap} returning a {@link Bitmap} with unpremultiplied
-     *  pixels. See {@link Bitmap#isPremultiplied}. This is incompatible with
-     *  {@link #decodeDrawable}; attempting to decode an unpremultiplied
-     *  {@link Drawable} will throw an {@link java.lang.IllegalStateException}.
-     *  </p>
+     *  pixels. See {@link Bitmap#isPremultiplied Bitmap.isPremultiplied()}.
+     *  This is incompatible with {@link #decodeDrawable decodeDrawable};
+     *  attempting to decode an unpremultiplied {@link Drawable} will throw an
+     *  {@link java.lang.IllegalStateException}. </p>
      *
      *  <p>Like all setters on ImageDecoder, this must be called inside
-     *  {@link OnHeaderDecodedListener#onHeaderDecoded}.</p>
+     *  {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p>
      */
     public void setUnpremultipliedRequired(boolean unpremultipliedRequired) {
         mUnpremultipliedRequired = unpremultipliedRequired;
@@ -1072,6 +1222,9 @@
      *  {@link Bitmap}. For a {@code Drawable} or an immutable {@code Bitmap},
      *  this is the only way to process the image after decoding.</p>
      *
+     *  <p>If combined with {@link #setTargetSize} and/or {@link #setCrop},
+     *  {@link PostProcessor#onPostProcess} occurs last.</p>
+     *
      *  <p>If set on a nine-patch image, the nine-patch data is ignored.</p>
      *
      *  <p>For an animated image, the drawing commands drawn on the
@@ -1079,11 +1232,11 @@
      *  frame.</p>
      *
      *  <p>Like all setters on ImageDecoder, this must be called inside
-     *  {@link OnHeaderDecodedListener#onHeaderDecoded}.</p>
+     *  {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p>
      *
      */
-    public void setPostProcessor(@Nullable PostProcessor p) {
-        mPostProcessor = p;
+    public void setPostProcessor(@Nullable PostProcessor postProcessor) {
+        mPostProcessor = postProcessor;
     }
 
     /**
@@ -1098,18 +1251,18 @@
      *  Set (replace) the {@link OnPartialImageListener} on this object.
      *
      *  <p>Will be called if there is an error in the input. Without one, an
-     *  error will result in an Exception being thrown.</p>
+     *  error will result in an {@code Exception} being thrown.</p>
      *
      *  <p>Like all setters on ImageDecoder, this must be called inside
-     *  {@link OnHeaderDecodedListener#onHeaderDecoded}.</p>
+     *  {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p>
      *
      */
-    public void setOnPartialImageListener(@Nullable OnPartialImageListener l) {
-        mOnPartialImageListener = l;
+    public void setOnPartialImageListener(@Nullable OnPartialImageListener listener) {
+        mOnPartialImageListener = listener;
     }
 
     /**
-     *  Return the {@link OnPartialImageListener} currently set.
+     *  Return the {@link OnPartialImageListener OnPartialImageListener} currently set.
      */
     @Nullable
     public OnPartialImageListener getOnPartialImageListener() {
@@ -1122,14 +1275,14 @@
      *  <p>{@code subset} must be contained within the size set by
      *  {@link #setTargetSize} or the bounds of the image if setTargetSize was
      *  not called. Otherwise an {@link IllegalStateException} will be thrown by
-     *  {@link #decodeDrawable}/{@link #decodeBitmap}.</p>
+     *  {@link #decodeDrawable decodeDrawable}/{@link #decodeBitmap decodeBitmap}.</p>
      *
      *  <p>NOT intended as a replacement for
-     *  {@link BitmapRegionDecoder#decodeRegion}. This supports all formats,
-     *  but merely crops the output.</p>
+     *  {@link BitmapRegionDecoder#decodeRegion BitmapRegionDecoder.decodeRegion()}.
+     *  This supports all formats, but merely crops the output.</p>
      *
      *  <p>Like all setters on ImageDecoder, this must be called inside
-     *  {@link OnHeaderDecodedListener#onHeaderDecoded}.</p>
+     *  {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p>
      *
      */
     public void setCrop(@Nullable Rect subset) {
@@ -1151,7 +1304,7 @@
      *  rectangle during decode. Otherwise it will not be modified.
      *
      *  <p>Like all setters on ImageDecoder, this must be called inside
-     *  {@link OnHeaderDecodedListener#onHeaderDecoded}.</p>
+     *  {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p>
      *
      *  @hide
      */
@@ -1162,21 +1315,22 @@
     /**
      *  Specify whether the {@link Bitmap} should be mutable.
      *
-     *  <p>By default, a {@link Bitmap} created will be immutable, but that can
-     *  be changed with this call.</p>
+     *  <p>By default, a {@link Bitmap} created by {@link #decodeBitmap decodeBitmap}
+     *  will be immutable i.e. {@link Bitmap#isMutable() Bitmap.isMutable()} returns
+     *  {@code false}. This can be changed with {@code setMutableRequired(true)}.
      *
      *  <p>Mutable Bitmaps are incompatible with {@link #ALLOCATOR_HARDWARE},
      *  because {@link Bitmap.Config#HARDWARE} Bitmaps cannot be mutable.
      *  Attempting to combine them will throw an
      *  {@link java.lang.IllegalStateException}.</p>
      *
-     *  <p>Mutable Bitmaps are also incompatible with {@link #decodeDrawable},
+     *  <p>Mutable Bitmaps are also incompatible with {@link #decodeDrawable decodeDrawable},
      *  which would require retrieving the Bitmap from the returned Drawable in
      *  order to modify. Attempting to decode a mutable {@link Drawable} will
      *  throw an {@link java.lang.IllegalStateException}.</p>
      *
      *  <p>Like all setters on ImageDecoder, this must be called inside
-     *  {@link OnHeaderDecodedListener#onHeaderDecoded}.</p>
+     *  {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p>
      */
     public void setMutableRequired(boolean mutable) {
         mMutable = mutable;
@@ -1192,7 +1346,7 @@
     }
 
     /**
-     *  Return whether the {@link Bitmap} will be mutable.
+     *  Return whether the decoded {@link Bitmap} will be mutable.
      */
     public boolean isMutableRequired() {
         return mMutable;
@@ -1219,7 +1373,7 @@
      *  the memory used.</p>
      *
      *  <p>Like all setters on ImageDecoder, this must be called inside
-     *  {@link OnHeaderDecodedListener#onHeaderDecoded}.</p>
+     *  {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p>
      */
     public void setConserveMemory(boolean conserveMemory) {
         mConserveMemory = conserveMemory;
@@ -1244,12 +1398,12 @@
      *  no effect.</p>
      *
      *  <p>This is incompatible with {@link #ALLOCATOR_HARDWARE}. Trying to
-     *  combine them will result in {@link #decodeDrawable}/
-     *  {@link #decodeBitmap} throwing an
+     *  combine them will result in {@link #decodeDrawable decodeDrawable}/
+     *  {@link #decodeBitmap decodeBitmap} throwing an
      *  {@link java.lang.IllegalStateException}.</p>
      *
      *  <p>Like all setters on ImageDecoder, this must be called inside
-     *  {@link OnHeaderDecodedListener#onHeaderDecoded}.</p>
+     *  {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p>
      */
     public void setDecodeAsAlphaMaskEnabled(boolean enabled) {
         mDecodeAsAlphaMask = enabled;
@@ -1304,21 +1458,22 @@
     /**
      * Specify the desired {@link ColorSpace} for the output.
      *
-     * <p>If non-null, the decoder will try to decode into this
-     * color space. If it is null, which is the default, or the request cannot
-     * be met, the decoder will pick either the color space embedded in the
-     * image or the color space best suited for the requested image
-     * configuration (for instance {@link ColorSpace.Named#SRGB sRGB} for
-     * the {@link Bitmap.Config#ARGB_8888} configuration).</p>
+     * <p>If non-null, the decoder will try to decode into {@code colorSpace}.
+     * If it is null, which is the default, or the request cannot be met, the
+     * decoder will pick either the color space embedded in the image or the
+     * {@link ColorSpace} best suited for the requested image configuration
+     * (for instance {@link ColorSpace.Named#SRGB sRGB} for the
+     * {@link Bitmap.Config#ARGB_8888} configuration).</p>
      *
      * <p>{@link Bitmap.Config#RGBA_F16} always uses the
-     * {@link ColorSpace.Named#LINEAR_EXTENDED_SRGB scRGB} color space).
+     * {@link ColorSpace.Named#LINEAR_EXTENDED_SRGB scRGB} color space.
      * Bitmaps in other configurations without an embedded color space are
      * assumed to be in the {@link ColorSpace.Named#SRGB sRGB} color space.</p>
      *
      * <p class="note">Only {@link ColorSpace.Model#RGB} color spaces are
      * currently supported. An <code>IllegalArgumentException</code> will
-     * be thrown by the decode methods when setting a non-RGB color space
+     * be thrown by {@link #decodeDrawable decodeDrawable}/
+     * {@link #decodeBitmap decodeBitmap} when setting a non-RGB color space
      * such as {@link ColorSpace.Named#CIE_LAB Lab}.</p>
      *
      * <p class="note">The specified color space's transfer function must be
@@ -1328,12 +1483,20 @@
      * specified color space returns null.</p>
      *
      * <p>Like all setters on ImageDecoder, this must be called inside
-     * {@link OnHeaderDecodedListener#onHeaderDecoded}.</p>
+     * {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p>
      */
     public void setTargetColorSpace(ColorSpace colorSpace) {
         mDesiredColorSpace = colorSpace;
     }
 
+    /**
+     * Closes this resource, relinquishing any underlying resources. This method
+     * is invoked automatically on objects managed by the try-with-resources
+     * statement.
+     *
+     * <p>This is an implementation detail of {@link ImageDecoder}, and should
+     * never be called manually.</p>
+     */
     @Override
     public void close() {
         mCloseGuard.close();
@@ -1421,7 +1584,7 @@
      *  Create a {@link Drawable} from a {@code Source}.
      *
      *  @param src representing the encoded image.
-     *  @param listener for learning the {@link ImageInfo} and changing any
+     *  @param listener for learning the {@link ImageInfo ImageInfo} and changing any
      *      default settings on the {@code ImageDecoder}. This will be called on
      *      the same thread as {@code decodeDrawable} before that method returns.
      *      This is required in order to change any of the default settings.
@@ -1503,8 +1666,8 @@
     /**
      *  Create a {@link Drawable} from a {@code Source}.
      *
-     *  <p>Since there is no {@link OnHeaderDecodedListener}, the default
-     *  settings will be used. In order to change any settings, call
+     *  <p>Since there is no {@link OnHeaderDecodedListener OnHeaderDecodedListener},
+     *  the default settings will be used. In order to change any settings, call
      *  {@link #decodeDrawable(Source, OnHeaderDecodedListener)} instead.</p>
      *
      *  @param src representing the encoded image.
@@ -1523,7 +1686,7 @@
      *  Create a {@link Bitmap} from a {@code Source}.
      *
      *  @param src representing the encoded image.
-     *  @param listener for learning the {@link ImageInfo} and changing any
+     *  @param listener for learning the {@link ImageInfo ImageInfo} and changing any
      *      default settings on the {@code ImageDecoder}. This will be called on
      *      the same thread as {@code decodeBitmap} before that method returns.
      *      This is required in order to change any of the default settings.
@@ -1616,8 +1779,8 @@
     /**
      *  Create a {@link Bitmap} from a {@code Source}.
      *
-     *  <p>Since there is no {@link OnHeaderDecodedListener}, the default
-     *  settings will be used. In order to change any settings, call
+     *  <p>Since there is no {@link OnHeaderDecodedListener OnHeaderDecodedListener},
+     *  the default settings will be used. In order to change any settings, call
      *  {@link #decodeBitmap(Source, OnHeaderDecodedListener)} instead.</p>
      *
      *  @param src representing the encoded image.
diff --git a/graphics/java/android/graphics/PostProcessor.java b/graphics/java/android/graphics/PostProcessor.java
index b1712e9..6fed39b 100644
--- a/graphics/java/android/graphics/PostProcessor.java
+++ b/graphics/java/android/graphics/PostProcessor.java
@@ -16,25 +16,26 @@
 
 package android.graphics;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.graphics.drawable.AnimatedImageDrawable;
 import android.graphics.drawable.Drawable;
 
 /**
  *  Helper interface for adding custom processing to an image.
  *
- *  <p>The image being processed may be a {@link Drawable}, {@link Bitmap} or frame
- *  of an animated image produced by {@link ImageDecoder}. This is called before
- *  the requested object is returned.</p>
+ *  <p>The image being processed may be a {@link Drawable}, a {@link Bitmap}, or
+ *  a frame of an {@link AnimatedImageDrawable} produced by {@link ImageDecoder}.
+ *  This is called before the requested object is returned.</p>
  *
- *  <p>This custom processing also applies to image types that are otherwise
- *  immutable, such as {@link Bitmap.Config#HARDWARE}.</p>
+ *  <p>This custom processing can even be applied to images that will be returned
+ *  as immutable objects, such as a {@link Bitmap} with {@code Config}
+ *  {@link Bitmap.Config#HARDWARE} returned by {@link ImageDecoder}.</p>
  *
- *  <p>On an animated image, the callback will only be called once, but the drawing
- *  commands will be applied to each frame, as if the {@code Canvas} had been
- *  returned by {@link Picture#beginRecording}.<p>
+ *  <p>On an {@link AnimatedImageDrawable}, the callback will only be called once,
+ *  but the drawing commands will be applied to each frame, as if the {@link Canvas}
+ *  had been returned by {@link Picture#beginRecording Picture.beginRecording}.<p>
  *
- *  <p>Supplied to ImageDecoder via {@link ImageDecoder#setPostProcessor}.</p>
+ *  <p>Supplied to ImageDecoder via {@link ImageDecoder#setPostProcessor setPostProcessor}.</p>
  */
 public interface PostProcessor {
     /**
@@ -43,43 +44,44 @@
      *  <p>Drawing to the {@link Canvas} will behave as if the initial processing
      *  (e.g. decoding) already exists in the Canvas. An implementation can draw
      *  effects on top of this, or it can even draw behind it using
-     *  {@link PorterDuff.Mode#DST_OVER}. A common effect is to add transparency
-     *  to the corners to achieve rounded corners. That can be done with the
-     *  following code:</p>
+     *  {@link PorterDuff.Mode#DST_OVER PorterDuff.Mode.DST_OVER}. A common
+     *  effect is to add transparency to the corners to achieve rounded corners.
+     *  That can be done with the following code:</p>
      *
-     *  <code>
-     *      Path path = new Path();
-     *      path.setFillType(Path.FillType.INVERSE_EVEN_ODD);
-     *      int width = canvas.getWidth();
-     *      int height = canvas.getHeight();
-     *      path.addRoundRect(0, 0, width, height, 20, 20, Path.Direction.CW);
-     *      Paint paint = new Paint();
-     *      paint.setAntiAlias(true);
-     *      paint.setColor(Color.TRANSPARENT);
-     *      paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
-     *      canvas.drawPath(path, paint);
-     *      return PixelFormat.TRANSLUCENT;
-     *  </code>
+     *  <pre class="prettyprint">
+     *  Path path = new Path();
+     *  path.setFillType(Path.FillType.INVERSE_EVEN_ODD);
+     *  int width = canvas.getWidth();
+     *  int height = canvas.getHeight();
+     *  path.addRoundRect(0, 0, width, height, 20, 20, Path.Direction.CW);
+     *  Paint paint = new Paint();
+     *  paint.setAntiAlias(true);
+     *  paint.setColor(Color.TRANSPARENT);
+     *  paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
+     *  canvas.drawPath(path, paint);
+     *  return PixelFormat.TRANSLUCENT;
+     *  </pre>
      *
      *
      *  @param canvas The {@link Canvas} to draw to.
      *  @return Opacity of the result after drawing.
-     *      {@link PixelFormat#UNKNOWN} means that the implementation did not
-     *      change whether the image has alpha. Return this unless you added
-     *      transparency (e.g. with the code above, in which case you should
-     *      return {@code PixelFormat.TRANSLUCENT}) or you forced the image to
-     *      be opaque (e.g. by drawing everywhere with an opaque color and
-     *      {@code PorterDuff.Mode.DST_OVER}, in which case you should return
-     *      {@code PixelFormat.OPAQUE}).
-     *      {@link PixelFormat#TRANSLUCENT} means that the implementation added
-     *      transparency. This is safe to return even if the image already had
-     *      transparency. This is also safe to return if the result is opaque,
-     *      though it may draw more slowly.
-     *      {@link PixelFormat#OPAQUE} means that the implementation forced the
-     *      image to be opaque. This is safe to return even if the image was
-     *      already opaque.
-     *      {@link PixelFormat#TRANSPARENT} (or any other integer) is not
-     *      allowed, and will result in throwing an
+     *      {@link PixelFormat#UNKNOWN PixelFormat.UNKNOWN} means that the
+     *      implementation did not change whether the image has alpha. Return
+     *      this unless you added transparency (e.g. with the code above, in
+     *      which case you should return
+     *      {@link PixelFormat#TRANSLUCENT PixelFormat.TRANSLUCENT}) or you
+     *      forced the image to be opaque (e.g. by drawing everywhere with an
+     *      opaque color and {@link PorterDuff.Mode#DST_OVER PorterDuff.Mode.DST_OVER},
+     *      in which case you should return {@link PixelFormat#OPAQUE PixelFormat.OPAQUE}).
+     *      {@link PixelFormat#TRANSLUCENT PixelFormat.TRANSLUCENT} means that
+     *      the implementation added transparency. This is safe to return even
+     *      if the image already had transparency. This is also safe to return
+     *      if the result is opaque, though it may draw more slowly.
+     *      {@link PixelFormat#OPAQUE PixelFormat.OPAQUE} means that the
+     *      implementation forced the image to be opaque. This is safe to return
+     *      even if the image was already opaque.
+     *      {@link PixelFormat#TRANSPARENT PixelFormat.TRANSPARENT} (or any other
+     *      integer) is not allowed, and will result in throwing an
      *      {@link java.lang.IllegalArgumentException}.
      */
     @PixelFormat.Opacity
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_backspace_black_24dp.xml b/packages/SystemUI/res-keyguard/drawable/ic_backspace_black_24dp.xml
index 6edae4b..1f6b24b 100644
--- a/packages/SystemUI/res-keyguard/drawable/ic_backspace_black_24dp.xml
+++ b/packages/SystemUI/res-keyguard/drawable/ic_backspace_black_24dp.xml
@@ -21,5 +21,5 @@
         android:viewportHeight="24.0">
     <path
         android:fillColor="#FF000000"
-        android:pathData="M22,3H7C6.31,3 5.77,3.35 5.41,3.88l-5.04,7.57c-0.22,0.34 -0.22,0.77 0,1.11l5.04,7.56C5.77,20.64 6.31,21 7,21h15c1.1,0 2,-0.9 2,-2V5C24,3.9 23.1,3 22,3zM18.3,16.3L18.3,16.3c-0.39,0.39 -1.02,0.39 -1.41,0L14,13.41l-2.89,2.89c-0.39,0.39 -1.02,0.39 -1.41,0h0c-0.39,-0.39 -0.39,-1.02 0,-1.41L12.59,12L9.7,9.11c-0.39,-0.39 -0.39,-1.02 0,-1.41l0,0c0.39,-0.39 1.02,-0.39 1.41,0L14,10.59l2.89,-2.89c0.39,-0.39 1.02,-0.39 1.41,0v0c0.39,0.39 0.39,1.02 0,1.41L15.41,12l2.89,2.89C18.68,15.27 18.68,15.91 18.3,16.3z"/>
+        android:pathData="M9,15.59L12.59,12L9,8.41L10.41,7L14,10.59L17.59,7L19,8.41L15.41,12L19,15.59L17.59,17L14,13.41L10.41,17L9,15.59zM21,6H8l-4.5,6L8,18h13V6M21,4c1.1,0 2,0.9 2,2v12c0,1.1 -0.9,2 -2,2H8c-0.63,0 -1.22,-0.3 -1.6,-0.8L1,12l5.4,-7.2C6.78,4.3 7.37,4 8,4H21L21,4z"/>
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_account_circle.xml b/packages/SystemUI/res/drawable/ic_account_circle.xml
deleted file mode 100644
index 3c5f01b..0000000
--- a/packages/SystemUI/res/drawable/ic_account_circle.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2017 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="48.0dp"
-        android:height="48.0dp"
-        android:viewportWidth="48.0"
-        android:viewportHeight="48.0">
-    <path
-        android:pathData="M24,0C10.8,0 0,10.8 0,24s10.8,24 24,24s24,-10.8 24,-24S37.200001,0 24,0zM24,7.2c3.96,0 7.2,3.24 7.2,7.2s-3.24,7.2 -7.2,7.2s-7.2,-3.24 -7.2,-7.2S20.040001,7.2 24,7.2zM24,41.279999c-6,0 -11.28,-3.12 -14.4,-7.68c0.12,-4.8 9.6,-7.44 14.4,-7.44s14.28,2.64 14.4,7.44C35.279999,38.16 30,41.279999 24,41.279999z"
-        android:fillColor="?attr/wallpaperTextColor"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_brightness_thumb.xml b/packages/SystemUI/res/drawable/ic_brightness_thumb.xml
index 8281836..6f3da75 100644
--- a/packages/SystemUI/res/drawable/ic_brightness_thumb.xml
+++ b/packages/SystemUI/res/drawable/ic_brightness_thumb.xml
@@ -18,10 +18,12 @@
         android:height="24dp"
         android:viewportWidth="24.0"
         android:viewportHeight="24.0">
+
     <path
-        android:pathData="m18.250000,12.000000a6.250000,6.250000 0.000000,1.000000 1.000000,-12.500000 0.000000,6.250000 6.250000,0.000000 1.000000,1.000000 12.500000,0.000000z"
-        android:fillColor="@android:color/transparent" />
+        android:pathData="M18,14.48V18h-3.52L12,20.48L9.52,18H6v-3.52L3.52,12L6,9.52V6h3.52L12,3.52L14.48,6H18v3.52L20.48,12L18,14.48z"
+        android:fillColor="?android:attr/colorPrimary" />
+
     <path
-        android:pathData="M20,8.69L20,5c0,-0.55 -0.45,-1 -1,-1h-3.69l-2.6,-2.6a0.996,0.996 0,0 0,-1.41 0L8.69,4L5,4c-0.55,0 -1,0.45 -1,1v3.69l-2.6,2.6a0.996,0.996 0,0 0,0 1.41L4,15.3L4,19c0,0.55 0.45,1 1,1h3.69l2.6,2.6c0.39,0.39 1.02,0.39 1.41,0l2.6,-2.6L19,20c0.55,0 1,-0.45 1,-1v-3.69l2.6,-2.6a0.996,0.996 0,0 0,0 -1.41L20,8.69zM12,18.08c-3.36,0 -6.08,-2.73 -6.08,-6.08S8.64,5.92 12,5.92s6.08,2.73 6.08,6.08 -2.72,6.08 -6.08,6.08zM12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4z"
+        android:pathData=" M20,8.69 V4h-4.69L12,0.69L8.69,4H4v4.69L0.69,12L4,15.31V20h4.69L12,23.31L15.31,20H20v-4.69L23.31,12L20,8.69z M18,14.48V18h-3.52L12,20.48L9.52,18H6v-3.52L3.52,12L6,9.52V6h3.52L12,3.52L14.48,6H18v3.52L20.48,12L18,14.48z M12,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5s5,-2.24 5,-5S14.76,7 12,7z"
         android:fillColor="?android:attr/colorControlActivated" />
 </vector>
diff --git a/packages/SystemUI/res/layout/quick_settings_header_info.xml b/packages/SystemUI/res/layout/quick_settings_header_info.xml
index 54baa4a..5229f9b 100644
--- a/packages/SystemUI/res/layout/quick_settings_header_info.xml
+++ b/packages/SystemUI/res/layout/quick_settings_header_info.xml
@@ -41,14 +41,13 @@
         android:visibility="invisible">
 
         <ImageView
-            android:id="@+id/next_alarm_icon"
+            android:id="@+id/ringer_mode_icon"
             android:layout_width="@dimen/qs_header_alarm_icon_size"
             android:layout_height="@dimen/qs_header_alarm_icon_size"
-            android:src="@drawable/stat_sys_alarm"
             android:tint="?android:attr/textColorPrimary" />
 
         <TextView
-            android:id="@+id/next_alarm_text"
+            android:id="@+id/ringer_mode_text"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginStart="@dimen/qs_header_alarm_text_margin_start"
@@ -64,13 +63,14 @@
             android:backgroundTint="?android:attr/textColorPrimary" />
 
         <ImageView
-            android:id="@+id/ringer_mode_icon"
+            android:id="@+id/next_alarm_icon"
             android:layout_width="@dimen/qs_header_alarm_icon_size"
             android:layout_height="@dimen/qs_header_alarm_icon_size"
+            android:src="@drawable/stat_sys_alarm"
             android:tint="?android:attr/textColorPrimary" />
 
         <TextView
-            android:id="@+id/ringer_mode_text"
+            android:id="@+id/next_alarm_text"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginStart="@dimen/qs_header_alarm_text_margin_start"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4074042..da6ecc3 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1335,6 +1335,11 @@
     <string name="volume_ringer_status_vibrate">Vibrate</string>
     <string name="volume_ringer_status_silent">Mute</string>
 
+    <!-- Shown in the header of quick settings to indicate to the user that their phone ringer is on vibrate. [CHAR_LIMIT=NONE] -->
+    <string name="qs_status_phone_vibrate">Phone on vibrate</string>
+    <!-- Shown in the header of quick settings to indicate to the user that their phone ringer is on silent (muted). [CHAR_LIMIT=NONE] -->
+    <string name="qs_status_phone_muted">Phone muted</string>
+
     <string name="volume_stream_muted" translatable="false">%s silent</string>
     <string name="volume_stream_vibrate" translatable="false">%s vibrate</string>
     <string name="volume_stream_suppressed" translatable="false">%1$s silent — %2$s</string>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index df65d1f..224c367 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -172,11 +172,11 @@
         boolean ringerVisible = false;
         if (mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
             mRingerModeIcon.setImageResource(R.drawable.stat_sys_ringer_vibrate);
-            mRingerModeTextView.setText(R.string.volume_ringer_status_vibrate);
+            mRingerModeTextView.setText(R.string.qs_status_phone_vibrate);
             ringerVisible = true;
         } else if (mRingerMode == AudioManager.RINGER_MODE_SILENT) {
             mRingerModeIcon.setImageResource(R.drawable.stat_sys_ringer_silent);
-            mRingerModeTextView.setText(R.string.volume_ringer_status_silent);
+            mRingerModeTextView.setText(R.string.qs_status_phone_muted);
             ringerVisible = true;
         }
         mRingerModeIcon.setVisibility(ringerVisible ? View.VISIBLE : View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index 0f83078..862a6a4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -16,6 +16,8 @@
 
 import static com.android.systemui.qs.tileimpl.QSTileImpl.getColorForState;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -127,7 +129,6 @@
     }
 
     protected void setIcon(ImageView iv, QSTile.State state) {
-        updateIcon(iv, state);
         if (state.disabledByPolicy) {
             iv.setColorFilter(getContext().getColor(R.color.qs_tile_disabled_color));
         } else {
@@ -137,7 +138,7 @@
             int color = getColor(state.state);
             mState = state.state;
             if (iv.isShown() && mTint != 0) {
-                animateGrayScale(mTint, color, iv);
+                animateGrayScale(mTint, color, iv, () -> updateIcon(iv, state));
                 mTint = color;
             } else {
                 if (iv instanceof AlphaControlledSlashImageView) {
@@ -147,7 +148,10 @@
                     setTint(iv, color);
                 }
                 mTint = color;
+                updateIcon(iv, state);
             }
+        } else {
+            updateIcon(iv, state);
         }
     }
 
@@ -155,7 +159,8 @@
         return getColorForState(getContext(), state);
     }
 
-    public static void animateGrayScale(int fromColor, int toColor, ImageView iv) {
+    public static void animateGrayScale(int fromColor, int toColor, ImageView iv,
+            final Runnable endRunnable) {
         if (iv instanceof AlphaControlledSlashImageView) {
             ((AlphaControlledSlashImageView)iv)
                     .setFinalImageTintList(ColorStateList.valueOf(toColor));
@@ -175,10 +180,16 @@
 
                 setTint(iv, Color.argb(alpha, channel, channel, channel));
             });
-
+            anim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    endRunnable.run();
+                }
+            });
             anim.start();
         } else {
             setTint(iv, toColor);
+            endRunnable.run();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 8a1e4da..d8f7b71 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -41,6 +41,7 @@
 import com.android.systemui.qs.QSDetailItems;
 import com.android.systemui.qs.QSDetailItems.Item;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.tileimpl.QSIconViewImpl;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
@@ -60,6 +61,7 @@
 
     protected final WifiSignalCallback mSignalCallback = new WifiSignalCallback();
     private final ActivityStarter mActivityStarter;
+    private boolean mExpectDisabled;
 
     public WifiTile(QSHost host) {
         super(host);
@@ -120,6 +122,15 @@
         // Immediately enter transient state when turning on wifi.
         refreshState(wifiEnabled ? null : ARG_SHOW_TRANSIENT_ENABLING);
         mController.setWifiEnabled(!wifiEnabled);
+        mExpectDisabled = wifiEnabled;
+        if (mExpectDisabled) {
+            mHandler.postDelayed(() -> {
+                if (mExpectDisabled) {
+                    mExpectDisabled = false;
+                    refreshState();
+                }
+            }, QSIconViewImpl.QS_ANIM_LENGTH);
+        }
     }
 
     @Override
@@ -143,11 +154,13 @@
     @Override
     protected void handleUpdateState(SignalState state, Object arg) {
         if (DEBUG) Log.d(TAG, "handleUpdateState arg=" + arg);
-        final CallbackInfo cb;
-        if (arg != null && arg instanceof CallbackInfo) {
-            cb = (CallbackInfo) arg;
-        } else {
-            cb = mSignalCallback.mInfo;
+        final CallbackInfo cb = mSignalCallback.mInfo;
+        if (mExpectDisabled) {
+            if (cb.enabled) {
+                return; // Ignore updates until disabled event occurs.
+            } else {
+                mExpectDisabled = false;
+            }
         }
         boolean transientEnabling = arg == ARG_SHOW_TRANSIENT_ENABLING;
         boolean wifiConnected = cb.enabled && (cb.wifiSignalIconId > 0) && (cb.ssid != null);
@@ -288,7 +301,7 @@
             if (isShowingDetail()) {
                 mDetailAdapter.updateItems();
             }
-            refreshState(mInfo);
+            refreshState();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 04cb620..304a499 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -488,7 +488,7 @@
                 mUpdateFlingVelocity = vel;
             }
         } else if (mPanelClosedOnDown && !mHeadsUpManager.hasPinnedHeadsUp() && !mTracking
-                && !mStatusBar.isBouncerShowing()) {
+                && !mStatusBar.isBouncerShowing() && !mStatusBar.isKeyguardFadingAway()) {
             long timePassed = SystemClock.uptimeMillis() - mDownTime;
             if (timePassed < ViewConfiguration.getLongPressTimeout()) {
                 // Lets show the user that he can actually expand the panel
diff --git a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
index 4eb1930..53a9544 100644
--- a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
+++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
@@ -20,9 +20,13 @@
 import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY;
 import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkPolicy.LIMIT_DISABLED;
 
 import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
+import static com.android.server.net.NetworkPolicyManagerService.OPPORTUNISTIC_QUOTA_UNKNOWN;
 
 import android.app.usage.NetworkStatsManager;
 import android.app.usage.NetworkStatsManager.UsageCallback;
@@ -31,24 +35,34 @@
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.Network;
 import android.net.NetworkCapabilities;
+import android.net.NetworkIdentity;
+import android.net.NetworkPolicy;
 import android.net.NetworkPolicyManager;
 import android.net.NetworkRequest;
 import android.net.NetworkStats;
 import android.net.NetworkTemplate;
 import android.net.StringNetworkSpecifier;
+import android.os.BestClock;
 import android.os.Handler;
+import android.os.SystemClock;
 import android.telephony.TelephonyManager;
 import android.util.DebugUtils;
+import android.util.Pair;
 import android.util.Slog;
 
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
 import com.android.server.net.NetworkPolicyManagerInternal;
-import com.android.server.net.NetworkPolicyManagerService;
 import com.android.server.net.NetworkStatsManagerInternal;
 
+import java.time.Clock;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
 import java.util.Calendar;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Manages multipath data budgets.
@@ -69,6 +83,8 @@
 
     private final Context mContext;
     private final Handler mHandler;
+    private final Clock mClock;
+    private final Dependencies mDeps;
 
     private ConnectivityManager mCM;
     private NetworkPolicyManager mNPM;
@@ -80,9 +96,28 @@
     // STOPSHIP: replace this with a configurable mechanism.
     private static final long DEFAULT_DAILY_MULTIPATH_QUOTA = 2_500_000;
 
+    /**
+     * Divider to calculate opportunistic quota from user-set data limit or warning: 5% of user-set
+     * limit.
+     */
+    private static final int OPQUOTA_USER_SETTING_DIVIDER = 20;
+
+    public static class Dependencies {
+        public Clock getClock() {
+            return new BestClock(ZoneOffset.UTC, SystemClock.currentNetworkTimeClock(),
+                    Clock.systemUTC());
+        }
+    }
+
     public MultipathPolicyTracker(Context ctx, Handler handler) {
+        this(ctx, handler, new Dependencies());
+    }
+
+    public MultipathPolicyTracker(Context ctx, Handler handler, Dependencies deps) {
         mContext = ctx;
         mHandler = handler;
+        mClock = deps.getClock();
+        mDeps = deps;
         // Because we are initialized by the ConnectivityService constructor, we can't touch any
         // connectivity APIs. Service initialization is done in start().
     }
@@ -128,9 +163,11 @@
         private long mMultipathBudget;
         private final NetworkTemplate mNetworkTemplate;
         private final UsageCallback mUsageCallback;
+        private NetworkCapabilities mNetworkCapabilities;
 
         public MultipathTracker(Network network, NetworkCapabilities nc) {
             this.network = network;
+            this.mNetworkCapabilities = new NetworkCapabilities(nc);
             try {
                 subId = Integer.parseInt(
                         ((StringNetworkSpecifier) nc.getNetworkSpecifier()).toString());
@@ -167,32 +204,97 @@
             updateMultipathBudget();
         }
 
-        private long getDailyNonDefaultDataUsage() {
-            Calendar start = Calendar.getInstance();
-            Calendar end = (Calendar) start.clone();
-            start.set(Calendar.HOUR_OF_DAY, 0);
-            start.set(Calendar.MINUTE, 0);
-            start.set(Calendar.SECOND, 0);
-            start.set(Calendar.MILLISECOND, 0);
+        public void setNetworkCapabilities(NetworkCapabilities nc) {
+            mNetworkCapabilities = new NetworkCapabilities(nc);
+        }
 
+        // TODO: calculate with proper timezone information
+        private long getDailyNonDefaultDataUsage() {
+            final ZonedDateTime end =
+                    ZonedDateTime.ofInstant(mClock.instant(), ZoneId.systemDefault());
+            final ZonedDateTime start = end.truncatedTo(ChronoUnit.DAYS);
+
+            final long bytes = getNetworkTotalBytes(
+                    start.toInstant().toEpochMilli(),
+                    end.toInstant().toEpochMilli());
+            if (DBG) Slog.d(TAG, "Non-default data usage: " + bytes);
+            return bytes;
+        }
+
+        private long getNetworkTotalBytes(long start, long end) {
             try {
-                final long bytes = LocalServices.getService(NetworkStatsManagerInternal.class)
-                        .getNetworkTotalBytes(mNetworkTemplate, start.getTimeInMillis(),
-                                end.getTimeInMillis());
-                if (DBG) Slog.d(TAG, "Non-default data usage: " + bytes);
-                return bytes;
+                return LocalServices.getService(NetworkStatsManagerInternal.class)
+                        .getNetworkTotalBytes(mNetworkTemplate, start, end);
             } catch (RuntimeException e) {
                 Slog.w(TAG, "Failed to get data usage: " + e);
                 return -1;
             }
         }
 
+        private NetworkIdentity getTemplateMatchingNetworkIdentity(NetworkCapabilities nc) {
+            return new NetworkIdentity(
+                    ConnectivityManager.TYPE_MOBILE,
+                    0 /* subType, unused for template matching */,
+                    subscriberId,
+                    null /* networkId, unused for matching mobile networks */,
+                    !nc.hasCapability(NET_CAPABILITY_NOT_ROAMING),
+                    !nc.hasCapability(NET_CAPABILITY_NOT_METERED),
+                    false /* defaultNetwork, templates should have DEFAULT_NETWORK_ALL */);
+        }
+
+        private long getRemainingDailyBudget(long limitBytes,
+                Pair<ZonedDateTime, ZonedDateTime> cycle) {
+            final long start = cycle.first.toInstant().toEpochMilli();
+            final long end = cycle.second.toInstant().toEpochMilli();
+            final long totalBytes = getNetworkTotalBytes(start, end);
+            final long remainingBytes = totalBytes == -1 ? 0 : Math.max(0, limitBytes - totalBytes);
+            // 1 + ((end - now - 1) / millisInDay with integers is equivalent to:
+            // ceil((double)(end - now) / millisInDay)
+            final long remainingDays =
+                    1 + ((end - mClock.millis() - 1) / TimeUnit.DAYS.toMillis(1));
+
+            return remainingBytes / Math.max(1, remainingDays);
+        }
+
+        private long getUserPolicyOpportunisticQuotaBytes() {
+            // Keep the most restrictive applicable policy
+            long minQuota = Long.MAX_VALUE;
+            final NetworkIdentity identity = getTemplateMatchingNetworkIdentity(
+                    mNetworkCapabilities);
+
+            final NetworkPolicy[] policies = mNPM.getNetworkPolicies();
+            for (NetworkPolicy policy : policies) {
+                if (hasActiveCycle(policy) && policy.template.matches(identity)) {
+                    // Prefer user-defined warning, otherwise use hard limit
+                    final long policyBytes = (policy.warningBytes == LIMIT_DISABLED)
+                            ? policy.limitBytes : policy.warningBytes;
+
+                    if (policyBytes != LIMIT_DISABLED) {
+                        final long policyBudget = getRemainingDailyBudget(policyBytes,
+                                policy.cycleIterator().next());
+                        minQuota = Math.min(minQuota, policyBudget);
+                    }
+                }
+            }
+
+            if (minQuota == Long.MAX_VALUE) {
+                return OPPORTUNISTIC_QUOTA_UNKNOWN;
+            }
+
+            return minQuota / OPQUOTA_USER_SETTING_DIVIDER;
+        }
+
         void updateMultipathBudget() {
             long quota = LocalServices.getService(NetworkPolicyManagerInternal.class)
                     .getSubscriptionOpportunisticQuota(this.network, QUOTA_TYPE_MULTIPATH);
             if (DBG) Slog.d(TAG, "Opportunistic quota from data plan: " + quota + " bytes");
 
-            if (quota == NetworkPolicyManagerService.OPPORTUNISTIC_QUOTA_UNKNOWN) {
+            // Fallback to user settings-based quota if not available from phone plan
+            if (quota == OPPORTUNISTIC_QUOTA_UNKNOWN) {
+                quota = getUserPolicyOpportunisticQuotaBytes();
+            }
+
+            if (quota == OPPORTUNISTIC_QUOTA_UNKNOWN) {
                 // STOPSHIP: replace this with a configurable mechanism.
                 quota = DEFAULT_DAILY_MULTIPATH_QUOTA;
                 if (DBG) Slog.d(TAG, "Setting quota: " + quota + " bytes");
@@ -262,6 +364,11 @@
         }
     }
 
+    private static boolean hasActiveCycle(NetworkPolicy policy) {
+        return policy.hasCycle() && policy.lastLimitSnooze <
+                policy.cycleIterator().next().first.toInstant().toEpochMilli();
+    }
+
     // Only ever updated on the handler thread. Accessed from other binder threads to retrieve
     // the tracker for a specific network.
     private final ConcurrentHashMap <Network, MultipathTracker> mMultipathTrackers =
@@ -281,6 +388,7 @@
             public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
                 MultipathTracker existing = mMultipathTrackers.get(network);
                 if (existing != null) {
+                    existing.setNetworkCapabilities(nc);
                     existing.updateMultipathBudget();
                     return;
                 }
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 9d3f48b..45f1a2b 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -67,6 +67,8 @@
     public static final int DEXOPT_ENABLE_HIDDEN_API_CHECKS = 1 << 10;
     /** Indicates that dexopt should convert to CompactDex. */
     public static final int DEXOPT_GENERATE_COMPACT_DEX = 1 << 11;
+    /** Indicates that dexopt should generate an app image */
+    public static final int DEXOPT_GENERATE_APP_IMAGE = 1 << 12;
 
     // NOTE: keep in sync with installd
     public static final int FLAG_CLEAR_CACHE_ONLY = 1 << 8;
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 5a7893a..320affb 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -267,7 +267,7 @@
                 final StringBuilder builder = new StringBuilder();
 
                 // The current version.
-                builder.append("8 ");
+                builder.append("9 ");
 
                 builder.append("dexopt");
 
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 892fa12..ebab1a7 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -60,6 +60,7 @@
 import static com.android.server.pm.Installer.DEXOPT_IDLE_BACKGROUND_JOB;
 import static com.android.server.pm.Installer.DEXOPT_ENABLE_HIDDEN_API_CHECKS;
 import static com.android.server.pm.Installer.DEXOPT_GENERATE_COMPACT_DEX;
+import static com.android.server.pm.Installer.DEXOPT_GENERATE_APP_IMAGE;
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
 
@@ -521,6 +522,10 @@
         return getDexFlags(pkg.applicationInfo, compilerFilter, options);
     }
 
+    private boolean isAppImageEnabled() {
+        return SystemProperties.get("dalvik.vm.appimageformat", "").length() > 0;
+    }
+
     private int getDexFlags(ApplicationInfo info, String compilerFilter, DexoptOptions options) {
         int flags = info.flags;
         boolean debuggable = (flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
@@ -547,6 +552,14 @@
             case PackageManagerService.REASON_INSTALL:
                  generateCompactDex = false;
         }
+        // Use app images only if it is enabled and we are compiling
+        // profile-guided (so the app image doesn't conservatively contain all classes).
+        // If the app didn't request for the splits to be loaded in isolation or if it does not
+        // declare inter-split dependencies, then all the splits will be loaded in the base
+        // apk class loader (in the order of their definition, otherwise disable app images
+        // because they are unsupported for multiple class loaders. b/7269679
+        boolean generateAppImage = isProfileGuidedFilter && (info.splitDependencies == null ||
+                !info.requestsIsolatedSplitLoading()) && isAppImageEnabled();
         int dexFlags =
                 (isPublic ? DEXOPT_PUBLIC : 0)
                 | (debuggable ? DEXOPT_DEBUGGABLE : 0)
@@ -554,6 +567,7 @@
                 | (options.isBootComplete() ? DEXOPT_BOOTCOMPLETE : 0)
                 | (options.isDexoptIdleBackgroundJob() ? DEXOPT_IDLE_BACKGROUND_JOB : 0)
                 | (generateCompactDex ? DEXOPT_GENERATE_COMPACT_DEX : 0)
+                | (generateAppImage ? DEXOPT_GENERATE_APP_IMAGE : 0)
                 | hiddenApiFlag;
         return adjustDexoptFlags(dexFlags);
     }
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index d190432..d5ff2dd 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -16,21 +16,21 @@
 
 package android.net.apf;
 
+import static android.net.util.NetworkConstants.*;
 import static android.system.OsConstants.*;
-
 import static com.android.internal.util.BitUtils.bytesToBEInt;
 import static com.android.internal.util.BitUtils.getUint16;
 import static com.android.internal.util.BitUtils.getUint32;
 import static com.android.internal.util.BitUtils.getUint8;
-import static com.android.internal.util.BitUtils.uint16;
 import static com.android.internal.util.BitUtils.uint32;
-import static com.android.internal.util.BitUtils.uint8;
 
-import android.os.SystemClock;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.NetworkUtils;
-import android.net.apf.ApfGenerator;
 import android.net.apf.ApfGenerator.IllegalInstructionException;
 import android.net.apf.ApfGenerator.Register;
 import android.net.ip.IpClient;
@@ -39,31 +39,29 @@
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.RaEvent;
 import android.net.util.InterfaceParams;
+import android.os.PowerManager;
+import android.os.SystemClock;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.PacketSocketAddress;
 import android.text.format.DateUtils;
 import android.util.Log;
 import android.util.Pair;
-
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.HexDump;
 import com.android.internal.util.IndentingPrintWriter;
-
 import java.io.FileDescriptor;
 import java.io.IOException;
-import java.lang.Thread;
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.SocketException;
 import java.net.UnknownHostException;
-import java.nio.ByteBuffer;
 import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
-
 import libcore.io.IoBridge;
 
 /**
@@ -215,10 +213,6 @@
             { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
 
     private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN;
-    private static final int ICMP6_ROUTER_SOLICITATION = 133;
-    private static final int ICMP6_ROUTER_ADVERTISEMENT = 134;
-    private static final int ICMP6_NEIGHBOR_SOLICITATION = 135;
-    private static final int ICMP6_NEIGHBOR_ANNOUNCEMENT = 136;
 
     // NOTE: this must be added to the IPv4 header length in IPV4_HEADER_SIZE_MEMORY_SLOT
     private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 2;
@@ -258,9 +252,26 @@
     private long mUniqueCounter;
     @GuardedBy("this")
     private boolean mMulticastFilter;
+    @GuardedBy("this")
+    private boolean mInDozeMode;
     private final boolean mDrop802_3Frames;
     private final int[] mEthTypeBlackList;
 
+    // Detects doze mode state transitions.
+    private final BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)) {
+                PowerManager powerManager =
+                        (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+                final boolean deviceIdle = powerManager.isDeviceIdleMode();
+                setDozeMode(deviceIdle);
+            }
+        }
+    };
+    private final Context mContext;
+
     // Our IPv4 address, if we have just one, otherwise null.
     @GuardedBy("this")
     private byte[] mIPv4Address;
@@ -269,13 +280,14 @@
     private int mIPv4PrefixLength;
 
     @VisibleForTesting
-    ApfFilter(ApfConfiguration config, InterfaceParams ifParams,
+    ApfFilter(Context context, ApfConfiguration config, InterfaceParams ifParams,
             IpClient.Callback ipClientCallback, IpConnectivityLog log) {
         mApfCapabilities = config.apfCapabilities;
         mIpClientCallback = ipClientCallback;
         mInterfaceParams = ifParams;
         mMulticastFilter = config.multicastFilter;
         mDrop802_3Frames = config.ieee802_3Filter;
+        mContext = context;
 
         // Now fill the black list from the passed array
         mEthTypeBlackList = filterEthTypeBlackList(config.ethTypeBlackList);
@@ -284,6 +296,10 @@
 
         // TODO: ApfFilter should not generate programs until IpClient sends provisioning success.
         maybeStartFilter();
+
+        // Listen for doze-mode transition changes to enable/disable the IPv6 multicast filter.
+        mContext.registerReceiver(mDeviceIdleReceiver,
+                new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED));
     }
 
     private void log(String s) {
@@ -522,7 +538,7 @@
             // to our packet socket. b/29586253
             if (getUint16(mPacket, ETH_ETHERTYPE_OFFSET) != ETH_P_IPV6 ||
                     getUint8(mPacket, IPV6_NEXT_HEADER_OFFSET) != IPPROTO_ICMPV6 ||
-                    getUint8(mPacket, ICMP6_TYPE_OFFSET) != ICMP6_ROUTER_ADVERTISEMENT) {
+                    getUint8(mPacket, ICMP6_TYPE_OFFSET) != ICMPV6_ROUTER_ADVERTISEMENT) {
                 throw new InvalidRaException("Not an ICMP6 router advertisement");
             }
 
@@ -889,10 +905,11 @@
     private void generateIPv6FilterLocked(ApfGenerator gen) throws IllegalInstructionException {
         // Here's a basic summary of what the IPv6 filter program does:
         //
-        // if it's not ICMPv6:
-        //   if it's multicast and we're dropping multicast:
-        //     drop
-        //   pass
+        // if we're dropping multicast
+        //   if it's not IPCMv6 or it's ICMPv6 but we're in doze mode:
+        //     if it's multicast:
+        //       drop
+        //     pass
         // if it's ICMPv6 RS to any:
         //   drop
         // if it's ICMPv6 NA to ff02::1:
@@ -902,28 +919,44 @@
 
         // Drop multicast if the multicast filter is enabled.
         if (mMulticastFilter) {
-            // Don't touch ICMPv6 multicast here, we deal with it in more detail later.
-            String skipIpv6MulticastFilterLabel = "skipIPv6MulticastFilter";
-            gen.addJumpIfR0Equals(IPPROTO_ICMPV6, skipIpv6MulticastFilterLabel);
+            final String skipIPv6MulticastFilterLabel = "skipIPv6MulticastFilter";
+            final String dropAllIPv6MulticastsLabel = "dropAllIPv6Multicast";
 
-            // Drop all other packets sent to ff00::/8.
+            // While in doze mode, drop ICMPv6 multicast pings, let the others pass.
+            // While awake, let all ICMPv6 multicasts through.
+            if (mInDozeMode) {
+                // Not ICMPv6? -> Proceed to multicast filtering
+                gen.addJumpIfR0NotEquals(IPPROTO_ICMPV6, dropAllIPv6MulticastsLabel);
+
+                // ICMPv6 but not ECHO? -> Skip the multicast filter.
+                // (ICMPv6 ECHO requests will go through the multicast filter below).
+                gen.addLoad8(Register.R0, ICMP6_TYPE_OFFSET);
+                gen.addJumpIfR0NotEquals(ICMPV6_ECHO_REQUEST_TYPE, skipIPv6MulticastFilterLabel);
+            } else {
+                gen.addJumpIfR0Equals(IPPROTO_ICMPV6, skipIPv6MulticastFilterLabel);
+            }
+
+            // Drop all other packets sent to ff00::/8 (multicast prefix).
+            gen.defineLabel(dropAllIPv6MulticastsLabel);
             gen.addLoad8(Register.R0, IPV6_DEST_ADDR_OFFSET);
             gen.addJumpIfR0Equals(0xff, gen.DROP_LABEL);
-            // Not multicast and not ICMPv6. Pass.
+            // Not multicast. Pass.
             gen.addJump(gen.PASS_LABEL);
-            gen.defineLabel(skipIpv6MulticastFilterLabel);
+            gen.defineLabel(skipIPv6MulticastFilterLabel);
         } else {
             // If not ICMPv6, pass.
             gen.addJumpIfR0NotEquals(IPPROTO_ICMPV6, gen.PASS_LABEL);
         }
 
+        // If we got this far, the packet is ICMPv6.  Drop some specific types.
+
         // Add unsolicited multicast neighbor announcements filter
         String skipUnsolicitedMulticastNALabel = "skipUnsolicitedMulticastNA";
         gen.addLoad8(Register.R0, ICMP6_TYPE_OFFSET);
         // Drop all router solicitations (b/32833400)
-        gen.addJumpIfR0Equals(ICMP6_ROUTER_SOLICITATION, gen.DROP_LABEL);
+        gen.addJumpIfR0Equals(ICMPV6_ROUTER_SOLICITATION, gen.DROP_LABEL);
         // If not neighbor announcements, skip filter.
-        gen.addJumpIfR0NotEquals(ICMP6_NEIGHBOR_ANNOUNCEMENT, skipUnsolicitedMulticastNALabel);
+        gen.addJumpIfR0NotEquals(ICMPV6_NEIGHBOR_ADVERTISEMENT, skipUnsolicitedMulticastNALabel);
         // If to ff02::1, drop.
         // TODO: Drop only if they don't contain the address of on-link neighbours.
         gen.addLoadImmediate(Register.R0, IPV6_DEST_ADDR_OFFSET);
@@ -1168,9 +1201,9 @@
      * Create an {@link ApfFilter} if {@code apfCapabilities} indicates support for packet
      * filtering using APF programs.
      */
-    public static ApfFilter maybeCreate(ApfConfiguration config,
+    public static ApfFilter maybeCreate(Context context, ApfConfiguration config,
             InterfaceParams ifParams, IpClient.Callback ipClientCallback) {
-        if (config == null || ifParams == null) return null;
+        if (context == null || config == null || ifParams == null) return null;
         ApfCapabilities apfCapabilities =  config.apfCapabilities;
         if (apfCapabilities == null) return null;
         if (apfCapabilities.apfVersionSupported == 0) return null;
@@ -1187,7 +1220,8 @@
             Log.e(TAG, "Unsupported APF version: " + apfCapabilities.apfVersionSupported);
             return null;
         }
-        return new ApfFilter(config, ifParams, ipClientCallback, new IpConnectivityLog());
+
+        return new ApfFilter(context, config, ifParams, ipClientCallback, new IpConnectivityLog());
     }
 
     public synchronized void shutdown() {
@@ -1197,12 +1231,11 @@
             mReceiveThread = null;
         }
         mRas.clear();
+        mContext.unregisterReceiver(mDeviceIdleReceiver);
     }
 
     public synchronized void setMulticastFilter(boolean isEnabled) {
-        if (mMulticastFilter == isEnabled) {
-            return;
-        }
+        if (mMulticastFilter == isEnabled) return;
         mMulticastFilter = isEnabled;
         if (!isEnabled) {
             mNumProgramUpdatesAllowingMulticast++;
@@ -1210,6 +1243,13 @@
         installNewProgramLocked();
     }
 
+    @VisibleForTesting
+    public synchronized void setDozeMode(boolean isEnabled) {
+        if (mInDozeMode == isEnabled) return;
+        mInDozeMode = isEnabled;
+        installNewProgramLocked();
+    }
+
     /** Find the single IPv4 LinkAddress if there is one, otherwise return null. */
     private static LinkAddress findIPv4LinkAddress(LinkProperties lp) {
         LinkAddress ipv4Address = null;
diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java
index 9863370..a184b22 100644
--- a/services/net/java/android/net/ip/IpClient.java
+++ b/services/net/java/android/net/ip/IpClient.java
@@ -1490,7 +1490,7 @@
                     mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames);
             apfConfig.ethTypeBlackList =
                     mContext.getResources().getIntArray(R.array.config_apfEthTypeBlackList);
-            mApfFilter = ApfFilter.maybeCreate(apfConfig, mInterfaceParams, mCallback);
+            mApfFilter = ApfFilter.maybeCreate(mContext, apfConfig, mInterfaceParams, mCallback);
             // TODO: investigate the effects of any multicast filtering racing/interfering with the
             // rest of this IP configuration startup.
             if (mApfFilter == null) {
diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java
index 984c9f8..53fd01f 100644
--- a/services/net/java/android/net/util/NetworkConstants.java
+++ b/services/net/java/android/net/util/NetworkConstants.java
@@ -136,6 +136,8 @@
      *     - https://tools.ietf.org/html/rfc4861
      */
     public static final int ICMPV6_HEADER_MIN_LEN = 4;
+    public static final int ICMPV6_ECHO_REQUEST_TYPE = 128;
+    public static final int ICMPV6_ECHO_REPLY_TYPE = 129;
     public static final int ICMPV6_ROUTER_SOLICITATION    = 133;
     public static final int ICMPV6_ROUTER_ADVERTISEMENT   = 134;
     public static final int ICMPV6_NEIGHBOR_SOLICITATION  = 135;
@@ -147,7 +149,6 @@
     public static final int ICMPV6_ND_OPTION_TLLA = 2;
     public static final int ICMPV6_ND_OPTION_MTU  = 5;
 
-    public static final int ICMPV6_ECHO_REQUEST_TYPE = 128;
 
     /**
      * UDP constants.
diff --git a/tests/net/java/android/net/apf/ApfTest.java b/tests/net/java/android/net/apf/ApfTest.java
index 9b75a50..fef702e 100644
--- a/tests/net/java/android/net/apf/ApfTest.java
+++ b/tests/net/java/android/net/apf/ApfTest.java
@@ -16,6 +16,7 @@
 
 package android.net.apf;
 
+import static android.net.util.NetworkConstants.*;
 import static android.system.OsConstants.*;
 import static com.android.internal.util.BitUtils.bytesToBEInt;
 import static com.android.internal.util.BitUtils.put;
@@ -26,6 +27,7 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.verify;
 
+import android.content.Context;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.NetworkUtils;
@@ -82,6 +84,7 @@
     private static final int TIMEOUT_MS = 500;
 
     @Mock IpConnectivityLog mLog;
+    @Mock Context mContext;
 
     @Before
     public void setUp() throws Exception {
@@ -633,9 +636,9 @@
         private FileDescriptor mWriteSocket;
         private final long mFixedTimeMs = SystemClock.elapsedRealtime();
 
-        public TestApfFilter(ApfConfiguration config, IpManager.Callback ipManagerCallback,
-                IpConnectivityLog log) throws Exception {
-            super(config, InterfaceParams.getByName("lo"), ipManagerCallback, log);
+        public TestApfFilter(Context context, ApfConfiguration config,
+                IpManager.Callback ipManagerCallback, IpConnectivityLog log) throws Exception {
+            super(context, config, InterfaceParams.getByName("lo"), ipManagerCallback, log);
         }
 
         // Pretend an RA packet has been received and show it to ApfFilter.
@@ -757,6 +760,17 @@
     private static final byte[] ANOTHER_IPV4_ADDR        = {10, 0, 0, 2};
     private static final byte[] IPV4_ANY_HOST_ADDR       = {0, 0, 0, 0};
 
+    // Helper to initialize a default apfFilter.
+    private ApfFilter setupApfFilter(IpManager.Callback ipManagerCallback, ApfConfiguration config)
+            throws Exception {
+        LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
+        LinkProperties lp = new LinkProperties();
+        lp.addLinkAddress(link);
+        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipManagerCallback, mLog);
+        apfFilter.setLinkProperties(lp);
+        return apfFilter;
+    }
+
     @Test
     public void testApfFilterIPv4() throws Exception {
         MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
@@ -766,7 +780,7 @@
 
         ApfConfiguration config = getDefaultConfig();
         config.multicastFilter = DROP_MULTICAST;
-        TestApfFilter apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
+        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipManagerCallback, mLog);
         apfFilter.setLinkProperties(lp);
 
         byte[] program = ipManagerCallback.getApfProgram();
@@ -818,7 +832,7 @@
     public void testApfFilterIPv6() throws Exception {
         MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
         ApfConfiguration config = getDefaultConfig();
-        TestApfFilter apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
+        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipManagerCallback, mLog);
         byte[] program = ipManagerCallback.getApfProgram();
 
         // Verify empty IPv6 packet is passed
@@ -861,7 +875,7 @@
 
         ApfConfiguration config = getDefaultConfig();
         config.ieee802_3Filter = DROP_802_3_FRAMES;
-        TestApfFilter apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
+        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipManagerCallback, mLog);
         apfFilter.setLinkProperties(lp);
 
         byte[] program = ipManagerCallback.getApfProgram();
@@ -925,7 +939,7 @@
         apfFilter.shutdown();
         config.multicastFilter = DROP_MULTICAST;
         config.ieee802_3Filter = DROP_802_3_FRAMES;
-        apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
+        apfFilter = new TestApfFilter(mContext, config, ipManagerCallback, mLog);
         apfFilter.setLinkProperties(lp);
         program = ipManagerCallback.getApfProgram();
         assertDrop(program, mcastv4packet.array());
@@ -941,16 +955,47 @@
     }
 
     @Test
+    public void testApfFilterMulticastPingWhileDozing() throws Exception {
+        MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
+        ApfFilter apfFilter = setupApfFilter(ipManagerCallback, getDefaultConfig());
+
+        // Construct a multicast ICMPv6 ECHO request.
+        final byte[] multicastIpv6Addr = {(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb};
+        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+        packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
+        packet.put(ICMP6_TYPE_OFFSET, (byte)ICMPV6_ECHO_REQUEST_TYPE);
+        put(packet, IPV6_DEST_ADDR_OFFSET, multicastIpv6Addr);
+
+        // Normally, we let multicast pings alone...
+        assertPass(ipManagerCallback.getApfProgram(), packet.array());
+
+        // ...and even while dozing...
+        apfFilter.setDozeMode(true);
+        assertPass(ipManagerCallback.getApfProgram(), packet.array());
+
+        // ...but when the multicast filter is also enabled, drop the multicast pings to save power.
+        apfFilter.setMulticastFilter(true);
+        assertDrop(ipManagerCallback.getApfProgram(), packet.array());
+
+        // However, we should still let through all other ICMPv6 types.
+        ByteBuffer raPacket = ByteBuffer.wrap(packet.array().clone());
+        raPacket.put(ICMP6_TYPE_OFFSET, (byte)ICMPV6_ROUTER_ADVERTISEMENT);
+        assertPass(ipManagerCallback.getApfProgram(), raPacket.array());
+
+        // Now wake up from doze mode to ensure that we no longer drop the packets.
+        // (The multicast filter is still enabled at this point).
+        apfFilter.setDozeMode(false);
+        assertPass(ipManagerCallback.getApfProgram(), packet.array());
+
+        apfFilter.shutdown();
+    }
+
+    @Test
     public void testApfFilter802_3() throws Exception {
         MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
-        LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
-        LinkProperties lp = new LinkProperties();
-        lp.addLinkAddress(link);
-
         ApfConfiguration config = getDefaultConfig();
-        TestApfFilter apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
-        apfFilter.setLinkProperties(lp);
-
+        ApfFilter apfFilter = setupApfFilter(ipManagerCallback, config);
         byte[] program = ipManagerCallback.getApfProgram();
 
         // Verify empty packet of 100 zero bytes is passed
@@ -970,8 +1015,7 @@
         ipManagerCallback.resetApfProgramWait();
         apfFilter.shutdown();
         config.ieee802_3Filter = DROP_802_3_FRAMES;
-        apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
-        apfFilter.setLinkProperties(lp);
+        apfFilter = setupApfFilter(ipManagerCallback, config);
         program = ipManagerCallback.getApfProgram();
 
         // Verify that IEEE802.3 frame is dropped
@@ -992,18 +1036,13 @@
 
     @Test
     public void testApfFilterEthTypeBL() throws Exception {
-        MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
-        LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
-        LinkProperties lp = new LinkProperties();
-        lp.addLinkAddress(link);
         final int[] emptyBlackList = {};
         final int[] ipv4BlackList = {ETH_P_IP};
         final int[] ipv4Ipv6BlackList = {ETH_P_IP, ETH_P_IPV6};
 
+        MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
         ApfConfiguration config = getDefaultConfig();
-        TestApfFilter apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
-        apfFilter.setLinkProperties(lp);
-
+        ApfFilter apfFilter = setupApfFilter(ipManagerCallback, config);
         byte[] program = ipManagerCallback.getApfProgram();
 
         // Verify empty packet of 100 zero bytes is passed
@@ -1023,8 +1062,7 @@
         ipManagerCallback.resetApfProgramWait();
         apfFilter.shutdown();
         config.ethTypeBlackList = ipv4BlackList;
-        apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
-        apfFilter.setLinkProperties(lp);
+        apfFilter = setupApfFilter(ipManagerCallback, config);
         program = ipManagerCallback.getApfProgram();
 
         // Verify that IPv4 frame will be dropped
@@ -1039,8 +1077,7 @@
         ipManagerCallback.resetApfProgramWait();
         apfFilter.shutdown();
         config.ethTypeBlackList = ipv4Ipv6BlackList;
-        apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
-        apfFilter.setLinkProperties(lp);
+        apfFilter = setupApfFilter(ipManagerCallback, config);
         program = ipManagerCallback.getApfProgram();
 
         // Verify that IPv4 frame will be dropped
@@ -1081,7 +1118,7 @@
         ApfConfiguration config = getDefaultConfig();
         config.multicastFilter = DROP_MULTICAST;
         config.ieee802_3Filter = DROP_802_3_FRAMES;
-        TestApfFilter apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
+        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipManagerCallback, mLog);
 
         // Verify initially ARP request filter is off, and GARP filter is on.
         verifyArpFilter(ipManagerCallback.getApfProgram(), PASS);
@@ -1205,7 +1242,7 @@
         ApfConfiguration config = getDefaultConfig();
         config.multicastFilter = DROP_MULTICAST;
         config.ieee802_3Filter = DROP_802_3_FRAMES;
-        TestApfFilter apfFilter = new TestApfFilter(config, ipManagerCallback, mLog);
+        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipManagerCallback, mLog);
         byte[] program = ipManagerCallback.getApfProgram();
 
         final int ROUTER_LIFETIME = 1000;
@@ -1351,7 +1388,7 @@
         ApfConfiguration config = getDefaultConfig();
         config.multicastFilter = DROP_MULTICAST;
         config.ieee802_3Filter = DROP_802_3_FRAMES;
-        TestApfFilter apfFilter = new TestApfFilter(config, cb, mLog);
+        TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mLog);
         for (int i = 0; i < 1000; i++) {
             byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
             r.nextBytes(packet);
@@ -1372,7 +1409,7 @@
         ApfConfiguration config = getDefaultConfig();
         config.multicastFilter = DROP_MULTICAST;
         config.ieee802_3Filter = DROP_802_3_FRAMES;
-        TestApfFilter apfFilter = new TestApfFilter(config, cb, mLog);
+        TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mLog);
         for (int i = 0; i < 1000; i++) {
             byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
             r.nextBytes(packet);