Merge changes from topic 'cherrypickWifiWatchdogResetOnTrigger' into nyc-mr1-dev

* changes:
  DO NOT MERGE StateMachine: remove final from SendMessage
  DO NOT MERGE WifiManager: add WIFI_MODE_NO_LOCKS_HELD
diff --git a/Android.mk b/Android.mk
index 320fe2f..0b33301 100644
--- a/Android.mk
+++ b/Android.mk
@@ -496,6 +496,10 @@
 
 LOCAL_RMTYPEDEFS := true
 
+ifeq ($(EMMA_INSTRUMENT_FRAMEWORK),true)
+LOCAL_EMMA_INSTRUMENT := true
+endif
+
 include $(BUILD_JAVA_LIBRARY)
 framework_module := $(LOCAL_INSTALLED_MODULE)
 
diff --git a/api/current.txt b/api/current.txt
index c631627..08c9fd6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -29362,6 +29362,7 @@
     method public android.os.Bundle getUserRestrictions();
     method public android.os.Bundle getUserRestrictions(android.os.UserHandle);
     method public boolean hasUserRestriction(java.lang.String);
+    method public boolean isDemoUser();
     method public boolean isQuietModeEnabled(android.os.UserHandle);
     method public boolean isSystemUser();
     method public boolean isUserAGoat();
diff --git a/api/system-current.txt b/api/system-current.txt
index 3fa518a..7aa5394 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -31887,6 +31887,7 @@
     method public android.os.Bundle getUserRestrictions();
     method public android.os.Bundle getUserRestrictions(android.os.UserHandle);
     method public boolean hasUserRestriction(java.lang.String);
+    method public boolean isDemoUser();
     method public boolean isManagedProfile();
     method public boolean isManagedProfile(int);
     method public boolean isQuietModeEnabled(android.os.UserHandle);
diff --git a/api/test-current.txt b/api/test-current.txt
index 993f388..003e50b 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -29433,6 +29433,7 @@
     method public android.os.Bundle getUserRestrictions();
     method public android.os.Bundle getUserRestrictions(android.os.UserHandle);
     method public boolean hasUserRestriction(java.lang.String);
+    method public boolean isDemoUser();
     method public boolean isQuietModeEnabled(android.os.UserHandle);
     method public boolean isSystemUser();
     method public boolean isUserAGoat();
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 8ccd5d2e..d6c0058 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -723,10 +723,10 @@
                 System.out.println("Complete");
             }
             mRepeat--;
-            if (mRepeat > 1) {
+            if (mRepeat > 0) {
                 mAm.unhandledBack();
             }
-        } while (mRepeat > 1);
+        } while (mRepeat > 0);
     }
 
     private void runForceStop() throws Exception {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 05f49c5..4104d72 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -762,14 +762,13 @@
     public Bundle extras = new Bundle();
 
     /**
-     * All pending intents in the notification extras (notification extras, actions extras,
-     * and remote input extras) as the system needs to be able to access them but touching
-     * the extras bundle in the system process is not safe because the bundle may contain
+     * All pending intents in the notification as the system needs to be able to access them but
+     * touching the extras bundle in the system process is not safe because the bundle may contain
      * custom parcelable objects.
      *
      * @hide
      */
-    public ArraySet<PendingIntent> extrasPendingIntents;
+    public ArraySet<PendingIntent> allPendingIntents;
 
     /**
      * {@link #extras} key: this is the title of the notification,
@@ -1593,7 +1592,7 @@
         // intents in extras are always written as the last entry.
         readFromParcelImpl(parcel);
         // Must be read last!
-        extrasPendingIntents = (ArraySet<PendingIntent>) parcel.readArraySet(null);
+        allPendingIntents = (ArraySet<PendingIntent>) parcel.readArraySet(null);
     }
 
     private void readFromParcelImpl(Parcel parcel)
@@ -1751,8 +1750,8 @@
             }
         }
 
-        if (!ArrayUtils.isEmpty(extrasPendingIntents)) {
-            that.extrasPendingIntents = new ArraySet<>(extrasPendingIntents);
+        if (!ArrayUtils.isEmpty(allPendingIntents)) {
+            that.allPendingIntents = new ArraySet<>(allPendingIntents);
         }
 
         if (this.actions != null) {
@@ -1878,15 +1877,15 @@
         // cannot look into the extras as there may be parcelables there that
         // the platform does not know how to handle. To go around that we have
         // an explicit list of the pending intents in the extras bundle.
-        final boolean collectPendingIntents = (extrasPendingIntents == null);
+        final boolean collectPendingIntents = (allPendingIntents == null);
         if (collectPendingIntents) {
             PendingIntent.setOnMarshaledListener(
                     (PendingIntent intent, Parcel out, int outFlags) -> {
                 if (parcel == out) {
-                    if (extrasPendingIntents == null) {
-                        extrasPendingIntents = new ArraySet<>();
+                    if (allPendingIntents == null) {
+                        allPendingIntents = new ArraySet<>();
                     }
-                    extrasPendingIntents.add(intent);
+                    allPendingIntents.add(intent);
                 }
             });
         }
@@ -1895,7 +1894,7 @@
             // want to intercept all pending events written to the pacel.
             writeToParcelImpl(parcel, flags);
             // Must be written last!
-            parcel.writeArraySet(extrasPendingIntents);
+            parcel.writeArraySet(allPendingIntents);
         } finally {
             if (collectPendingIntents) {
                 PendingIntent.setOnMarshaledListener(null);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b06568c..fbe16c5 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -132,6 +132,9 @@
             MATCH_SYSTEM_ONLY,
             MATCH_FACTORY_ONLY,
             MATCH_DEBUG_TRIAGED_MISSING,
+            GET_DISABLED_COMPONENTS,
+            GET_DISABLED_UNTIL_USED_COMPONENTS,
+            GET_UNINSTALLED_PACKAGES,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface PackageInfoFlags {}
@@ -143,6 +146,7 @@
             MATCH_UNINSTALLED_PACKAGES,
             MATCH_SYSTEM_ONLY,
             MATCH_DEBUG_TRIAGED_MISSING,
+            GET_UNINSTALLED_PACKAGES,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ApplicationInfoFlags {}
@@ -160,6 +164,9 @@
             MATCH_DIRECT_BOOT_UNAWARE,
             MATCH_SYSTEM_ONLY,
             MATCH_UNINSTALLED_PACKAGES,
+            GET_DISABLED_COMPONENTS,
+            GET_DISABLED_UNTIL_USED_COMPONENTS,
+            GET_UNINSTALLED_PACKAGES,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ComponentInfoFlags {}
@@ -178,6 +185,9 @@
             MATCH_DIRECT_BOOT_UNAWARE,
             MATCH_SYSTEM_ONLY,
             MATCH_UNINSTALLED_PACKAGES,
+            GET_DISABLED_COMPONENTS,
+            GET_DISABLED_UNTIL_USED_COMPONENTS,
+            GET_UNINSTALLED_PACKAGES,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ResolveInfoFlags {}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index f59a7dd..82cd448 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1876,7 +1876,7 @@
                     sa = res.obtainAttributes(parser,
                             com.android.internal.R.styleable.AndroidManifestUsesSdk);
 
-                    int minVers = 0;
+                    int minVers = 1;
                     String minCode = null;
                     int targetVers = 0;
                     String targetCode = null;
@@ -1903,9 +1903,6 @@
                         } else {
                             // If it's not a string, it's an integer.
                             targetVers = val.data;
-                            if (minVers == 0) {
-                                minVers = targetVers;
-                            }
                         }
                     }
 
diff --git a/core/java/android/net/ConnectivityMetricsLogger.java b/core/java/android/net/ConnectivityMetricsLogger.java
index 7373495..029c5bd 100644
--- a/core/java/android/net/ConnectivityMetricsLogger.java
+++ b/core/java/android/net/ConnectivityMetricsLogger.java
@@ -23,6 +23,8 @@
 import android.os.ServiceManager;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 /** {@hide} */
 @SystemApi
 public class ConnectivityMetricsLogger {
@@ -49,8 +51,14 @@
     private int mNumSkippedEvents;
 
     public ConnectivityMetricsLogger() {
-        mService = IConnectivityMetricsLogger.Stub.asInterface(ServiceManager.getService(
-                CONNECTIVITY_METRICS_LOGGER_SERVICE));
+        this(IConnectivityMetricsLogger.Stub.asInterface(
+                ServiceManager.getService(CONNECTIVITY_METRICS_LOGGER_SERVICE)));
+    }
+
+    /** {@hide} */
+    @VisibleForTesting
+    public ConnectivityMetricsLogger(IConnectivityMetricsLogger service) {
+        mService = service;
     }
 
     /**
@@ -153,11 +161,10 @@
     public boolean unregister(PendingIntent newEventsIntent) {
         try {
             mService.unregister(newEventsIntent);
+            return true;
         } catch (RemoteException e) {
             Log.e(TAG, "IConnectivityMetricsLogger.unregister", e);
             return false;
         }
-
-        return true;
     }
 }
diff --git a/core/java/android/net/metrics/DefaultNetworkEvent.java b/core/java/android/net/metrics/DefaultNetworkEvent.java
index f8b5992..b881fbb 100644
--- a/core/java/android/net/metrics/DefaultNetworkEvent.java
+++ b/core/java/android/net/metrics/DefaultNetworkEvent.java
@@ -25,7 +25,7 @@
  * {@hide}
  */
 @SystemApi
-public final class DefaultNetworkEvent extends IpConnectivityEvent implements Parcelable {
+public final class DefaultNetworkEvent implements Parcelable {
     // The ID of the network that has become the new default or NETID_UNSET if none.
     public final int netId;
     // The list of transport types of the new default network, for example TRANSPORT_WIFI, as
@@ -37,7 +37,8 @@
     public final boolean prevIPv4;
     public final boolean prevIPv6;
 
-    private DefaultNetworkEvent(int netId, int[] transportTypes,
+    /** {@hide} */
+    public DefaultNetworkEvent(int netId, int[] transportTypes,
                 int prevNetId, boolean prevIPv4, boolean prevIPv6) {
         this.netId = netId;
         this.transportTypes = transportTypes;
@@ -105,6 +106,5 @@
 
     public static void logEvent(
             int netId, int[] transports, int prevNetId, boolean hadIPv4, boolean hadIPv6) {
-        logEvent(new DefaultNetworkEvent(netId, transports, prevNetId, hadIPv4, hadIPv6));
     }
-};
+}
diff --git a/core/java/android/net/metrics/DhcpClientEvent.java b/core/java/android/net/metrics/DhcpClientEvent.java
index ec560bf..3fe68b4 100644
--- a/core/java/android/net/metrics/DhcpClientEvent.java
+++ b/core/java/android/net/metrics/DhcpClientEvent.java
@@ -24,11 +24,12 @@
  * {@hide}
  */
 @SystemApi
-public final class DhcpClientEvent extends IpConnectivityEvent implements Parcelable {
+public final class DhcpClientEvent implements Parcelable {
     public final String ifName;
     public final String msg;
 
-    private DhcpClientEvent(String ifName, String msg) {
+    /** {@hide} */
+    public DhcpClientEvent(String ifName, String msg) {
         this.ifName = ifName;
         this.msg = msg;
     }
@@ -64,6 +65,5 @@
     };
 
     public static void logStateEvent(String ifName, String state) {
-        logEvent(new DhcpClientEvent(ifName, state));
     }
-};
+}
diff --git a/core/java/android/net/metrics/DhcpErrorEvent.java b/core/java/android/net/metrics/DhcpErrorEvent.java
index 84795b8..4206886 100644
--- a/core/java/android/net/metrics/DhcpErrorEvent.java
+++ b/core/java/android/net/metrics/DhcpErrorEvent.java
@@ -27,7 +27,7 @@
  * {@hide} Event class used to record error events when parsing DHCP response packets.
  */
 @SystemApi
-public final class DhcpErrorEvent extends IpConnectivityEvent implements Parcelable {
+public final class DhcpErrorEvent implements Parcelable {
     public static final int L2_ERROR   = 1;
     public static final int L3_ERROR   = 2;
     public static final int L4_ERROR   = 3;
@@ -61,7 +61,8 @@
     // byte 3: optional code
     public final int errorCode;
 
-    private DhcpErrorEvent(String ifName, int errorCode) {
+    /** {@hide} */
+    public DhcpErrorEvent(String ifName, int errorCode) {
         this.ifName = ifName;
         this.errorCode = errorCode;
     }
@@ -92,11 +93,9 @@
     };
 
     public static void logParseError(String ifName, int errorCode) {
-        logEvent(new DhcpErrorEvent(ifName, errorCode));
     }
 
     public static void logReceiveError(String ifName) {
-        logEvent(new DhcpErrorEvent(ifName, RECEIVE_ERROR));
     }
 
     public static int errorCodeWithOption(int errorCode, int option) {
diff --git a/core/java/android/net/metrics/DnsEvent.java b/core/java/android/net/metrics/DnsEvent.java
index b94dda0..9eb8bdb 100644
--- a/core/java/android/net/metrics/DnsEvent.java
+++ b/core/java/android/net/metrics/DnsEvent.java
@@ -24,7 +24,7 @@
  * {@hide}
  */
 @SystemApi
-final public class DnsEvent extends IpConnectivityEvent implements Parcelable {
+final public class DnsEvent implements Parcelable {
     public final int netId;
 
     // The event type is currently only 1 or 2, so we store it as a byte.
@@ -37,7 +37,8 @@
     // queries.
     public final int[] latenciesMs;
 
-    private DnsEvent(int netId, byte[] eventTypes, byte[] returnCodes, int[] latenciesMs) {
+    /** {@hide} */
+    public DnsEvent(int netId, byte[] eventTypes, byte[] returnCodes, int[] latenciesMs) {
         this.netId = netId;
         this.eventTypes = eventTypes;
         this.returnCodes = returnCodes;
@@ -82,6 +83,5 @@
 
     public static void logEvent(
             int netId, byte[] eventTypes, byte[] returnCodes, int[] latenciesMs) {
-        logEvent(new DnsEvent(netId, eventTypes, returnCodes, latenciesMs));
     }
 }
diff --git a/core/java/android/net/metrics/IpConnectivityEvent.java b/core/java/android/net/metrics/IpConnectivityEvent.java
deleted file mode 100644
index d3771dc..0000000
--- a/core/java/android/net/metrics/IpConnectivityEvent.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.metrics;
-
-import android.os.Parcelable;
-
-/** {@hide} */
-public abstract class IpConnectivityEvent {
-    private static final IpConnectivityLog sMetricsLog = new IpConnectivityLog();
-
-    static <T extends IpConnectivityEvent & Parcelable> void logEvent(T event) {
-        sMetricsLog.log(System.currentTimeMillis(), event);
-    }
-}
diff --git a/core/java/android/net/metrics/IpConnectivityLog.java b/core/java/android/net/metrics/IpConnectivityLog.java
index 233ff74..a7c1d40 100644
--- a/core/java/android/net/metrics/IpConnectivityLog.java
+++ b/core/java/android/net/metrics/IpConnectivityLog.java
@@ -18,28 +18,40 @@
 
 import android.net.ConnectivityMetricsEvent;
 import android.net.ConnectivityMetricsLogger;
+import android.net.IConnectivityMetricsLogger;
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 /**
  * Specialization of the ConnectivityMetricsLogger class for recording IP connectivity events.
  * {@hide}
  */
-class IpConnectivityLog extends ConnectivityMetricsLogger {
+public class IpConnectivityLog extends ConnectivityMetricsLogger {
     private static String TAG = "IpConnectivityMetricsLogger";
     private static final boolean DBG = false;
 
+    public IpConnectivityLog() {
+        // mService initialized in super constructor.
+    }
+
+    @VisibleForTesting
+    public IpConnectivityLog(IConnectivityMetricsLogger service) {
+        super(service);
+    }
+
     /**
      * Log an IpConnectivity event. Contrary to logEvent(), this method does not
      * keep track of skipped events and is thread-safe for callers.
      *
      * @param timestamp is the epoch timestamp of the event in ms.
-     * @param data is a Parcelable IpConnectivityEvent instance representing the event.
+     * @param data is a Parcelable instance representing the event.
      *
      * @return true if the event was successfully logged.
      */
-    public <T extends IpConnectivityEvent & Parcelable> boolean log(long timestamp, T data) {
+    public boolean log(long timestamp, Parcelable data) {
         if (mService == null) {
             if (DBG) {
                 Log.d(TAG, CONNECTIVITY_METRICS_LOGGER_SERVICE + " service not ready");
@@ -67,4 +79,8 @@
             return false;
         }
     }
+
+    public void log(Parcelable event) {
+        log(System.currentTimeMillis(), event);
+    }
 }
diff --git a/core/java/android/net/metrics/IpManagerEvent.java b/core/java/android/net/metrics/IpManagerEvent.java
index 0940bd0..a390617 100644
--- a/core/java/android/net/metrics/IpManagerEvent.java
+++ b/core/java/android/net/metrics/IpManagerEvent.java
@@ -27,7 +27,7 @@
  * {@hide}
  */
 @SystemApi
-public final class IpManagerEvent extends IpConnectivityEvent implements Parcelable {
+public final class IpManagerEvent implements Parcelable {
 
     public static final int PROVISIONING_OK    = 1;
     public static final int PROVISIONING_FAIL  = 2;
@@ -37,7 +37,8 @@
     public final int eventType;
     public final long durationMs;
 
-    private IpManagerEvent(String ifName, int eventType, long duration) {
+    /** {@hide} */
+    public IpManagerEvent(String ifName, int eventType, long duration) {
         this.ifName = ifName;
         this.eventType = eventType;
         this.durationMs = duration;
@@ -71,7 +72,6 @@
     };
 
     public static void logEvent(int eventType, String ifName, long durationMs) {
-        logEvent(new IpManagerEvent(ifName, eventType, durationMs));
     }
 
     @Override
@@ -84,4 +84,4 @@
         static final SparseArray<String> constants = MessageUtils.findMessageNames(
                 new Class[]{IpManagerEvent.class}, new String[]{"PROVISIONING_", "COMPLETE_"});
     }
-};
+}
diff --git a/core/java/android/net/metrics/IpReachabilityEvent.java b/core/java/android/net/metrics/IpReachabilityEvent.java
index d40389c..7d02291 100644
--- a/core/java/android/net/metrics/IpReachabilityEvent.java
+++ b/core/java/android/net/metrics/IpReachabilityEvent.java
@@ -27,7 +27,7 @@
  * {@hide}
  */
 @SystemApi
-public final class IpReachabilityEvent extends IpConnectivityEvent implements Parcelable {
+public final class IpReachabilityEvent implements Parcelable {
 
     public static final int PROBE             = 1 << 8;
     public static final int NUD_FAILED        = 2 << 8;
@@ -41,7 +41,8 @@
     // byte 3: kernel errno from RTNetlink or IpReachabilityMonitor
     public final int eventType;
 
-    private IpReachabilityEvent(String ifName, int eventType) {
+    /** {@hide} */
+    public IpReachabilityEvent(String ifName, int eventType) {
         this.ifName = ifName;
         this.eventType = eventType;
     }
@@ -72,15 +73,12 @@
     };
 
     public static void logProbeEvent(String ifName, int nlErrorCode) {
-        logEvent(new IpReachabilityEvent(ifName, PROBE | (nlErrorCode & 0xFF)));
     }
 
     public static void logNudFailed(String ifName) {
-        logEvent(new IpReachabilityEvent(ifName, NUD_FAILED));
     }
 
     public static void logProvisioningLost(String ifName) {
-        logEvent(new IpReachabilityEvent(ifName, PROVISIONING_LOST));
     }
 
     @Override
@@ -94,4 +92,4 @@
                 MessageUtils.findMessageNames(new Class[]{IpReachabilityEvent.class},
                 new String[]{"PROBE", "PROVISIONING_", "NUD_"});
     }
-};
+}
diff --git a/core/java/android/net/metrics/NetworkEvent.java b/core/java/android/net/metrics/NetworkEvent.java
index 08c9c75..cdfe386 100644
--- a/core/java/android/net/metrics/NetworkEvent.java
+++ b/core/java/android/net/metrics/NetworkEvent.java
@@ -27,7 +27,7 @@
  * {@hide}
  */
 @SystemApi
-public final class NetworkEvent extends IpConnectivityEvent implements Parcelable {
+public final class NetworkEvent implements Parcelable {
 
     public static final int NETWORK_CONNECTED            = 1;
     public static final int NETWORK_VALIDATED            = 2;
@@ -41,12 +41,18 @@
     public final int eventType;
     public final long durationMs;
 
-    private NetworkEvent(int netId, int eventType, long durationMs) {
+    /** {@hide} */
+    public NetworkEvent(int netId, int eventType, long durationMs) {
         this.netId = netId;
         this.eventType = eventType;
         this.durationMs = durationMs;
     }
 
+    /** {@hide} */
+    public NetworkEvent(int netId, int eventType) {
+        this(netId, eventType, 0);
+    }
+
     private NetworkEvent(Parcel in) {
         netId = in.readInt();
         eventType = in.readInt();
@@ -75,15 +81,12 @@
     };
 
     public static void logEvent(int netId, int eventType) {
-        logEvent(new NetworkEvent(netId, eventType, 0));
     }
 
     public static void logValidated(int netId, long durationMs) {
-        logEvent(new NetworkEvent(netId, NETWORK_VALIDATED, durationMs));
     }
 
     public static void logCaptivePortalFound(int netId, long durationMs) {
-        logEvent(new NetworkEvent(netId, NETWORK_CAPTIVE_PORTAL_FOUND, durationMs));
     }
 
     @Override
@@ -96,4 +99,4 @@
         static final SparseArray<String> constants = MessageUtils.findMessageNames(
                 new Class[]{NetworkEvent.class}, new String[]{"NETWORK_"});
     }
-};
+}
diff --git a/core/java/android/net/metrics/ValidationProbeEvent.java b/core/java/android/net/metrics/ValidationProbeEvent.java
index 751c35f..d5ad0f6 100644
--- a/core/java/android/net/metrics/ValidationProbeEvent.java
+++ b/core/java/android/net/metrics/ValidationProbeEvent.java
@@ -27,7 +27,7 @@
  * {@hide}
  */
 @SystemApi
-public final class ValidationProbeEvent extends IpConnectivityEvent implements Parcelable {
+public final class ValidationProbeEvent implements Parcelable {
 
     public static final int PROBE_DNS   = 0;
     public static final int PROBE_HTTP  = 1;
@@ -42,7 +42,8 @@
     public final int probeType;
     public final int returnCode;
 
-    private ValidationProbeEvent(int netId, long durationMs, int probeType, int returnCode) {
+    /** @hide */
+    public ValidationProbeEvent(int netId, long durationMs, int probeType, int returnCode) {
         this.netId = netId;
         this.durationMs = durationMs;
         this.probeType = probeType;
@@ -84,7 +85,6 @@
     }
 
     public static void logEvent(int netId, long durationMs, int probeType, int returnCode) {
-        logEvent(new ValidationProbeEvent(netId, durationMs, probeType, returnCode));
     }
 
     @Override
@@ -97,4 +97,4 @@
         static final SparseArray<String> constants = MessageUtils.findMessageNames(
                 new Class[]{ValidationProbeEvent.class}, new String[]{"PROBE_"});
     }
-};
+}
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index b27cb32..eeb641d 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -81,4 +81,5 @@
     void clearSeedAccountData();
     boolean someUserHasSeedAccount(in String accountName, in String accountType);
     boolean isManagedProfile(int userId);
+    boolean isDemoUser(int userId);
 }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index d742206..a44a9ee 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -859,15 +859,17 @@
     }
 
     /**
-     * Checks if the calling app is running in a demo user.
-     * <p>
-     * Caller must hold the MANAGE_USERS permission.
+     * Checks if the calling app is running in a demo user. When running in a demo user,
+     * apps can be more helpful to the user, or explain their features in more detail.
+     *
      * @return whether the caller is a demo user.
-     * @hide
      */
     public boolean isDemoUser() {
-        UserInfo user = getUserInfo(UserHandle.myUserId());
-        return user != null && user.isDemo();
+        try {
+            return mService.isDemoUser(UserHandle.myUserId());
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
     }
 
     /**
diff --git a/core/jni/android_app_ApplicationLoaders.cpp b/core/jni/android_app_ApplicationLoaders.cpp
index 24ee959..3e7c039 100644
--- a/core/jni/android_app_ApplicationLoaders.cpp
+++ b/core/jni/android_app_ApplicationLoaders.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#define LOG_TAG "ApplicationLoaders"
+
 #include <nativehelper/ScopedUtfChars.h>
 #include <nativeloader/native_loader.h>
 #include <vulkan/vulkan_loader_data.h>
@@ -22,10 +24,17 @@
 
 static void setupVulkanLayerPath_native(JNIEnv* env, jobject clazz,
         jobject classLoader, jstring librarySearchPath) {
+    android_namespace_t* ns = android::FindNamespaceByClassLoader(env, classLoader);
     ScopedUtfChars layerPathChars(env, librarySearchPath);
+
     vulkan::LoaderData& loader_data = vulkan::LoaderData::GetInstance();
-    loader_data.layer_path = layerPathChars.c_str();
-    loader_data.app_namespace = android::FindNamespaceByClassLoader(env, classLoader);
+    if (loader_data.layer_path.empty()) {
+        loader_data.layer_path = layerPathChars.c_str();
+        loader_data.app_namespace = ns;
+    } else {
+        ALOGD("ignored Vulkan layer search path %s for namespace %p",
+                layerPathChars.c_str(), ns);
+    }
 }
 
 static const JNINativeMethod g_methods[] = {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3783dc8..3c71dd9 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -193,6 +193,7 @@
     <protected-broadcast android:name="android.btopp.intent.action.OPEN" />
     <protected-broadcast android:name="android.btopp.intent.action.OPEN_INBOUND" />
     <protected-broadcast android:name="android.btopp.intent.action.TRANSFER_COMPLETE" />
+    <protected-broadcast android:name="com.android.bluetooth.gatt.REFRESH_BATCHED_SCAN" />
     <protected-broadcast android:name="com.android.bluetooth.pbap.authchall" />
     <protected-broadcast android:name="com.android.bluetooth.pbap.userconfirmtimeout" />
     <protected-broadcast android:name="com.android.bluetooth.pbap.authresponse" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index ae2a9e4..a32faf8 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4382,7 +4382,7 @@
     <!-- Title of the dialog shown when user inactivity times out in retail demo mode [CHAR LIMIT=40] -->
     <string name="demo_user_inactivity_timeout_title">Reset device?</string>
     <!-- Warning message shown when user inactivity times out in retail demo mode [CHAR LIMIT=none] -->
-    <string name="demo_user_inactivity_timeout_countdown">You\'ll lose any changes and the demo will start again in <xliff:g id="timeout" example="9">%1$s</xliff:g> seconds\u2026</string>
+    <string name="demo_user_inactivity_timeout_countdown">You\u2019ll lose any changes and the demo will start again in <xliff:g id="timeout" example="9">%1$s</xliff:g> seconds\u2026</string>
     <!-- Text of button to allow user to abort countdown and continue current session in retail demo mode [CHAR LIMIT=40] -->
     <string name="demo_user_inactivity_timeout_left_button">Cancel</string>
     <!-- Text of button to allow user to abort countdown and immediately start another session in retail demo mode [CHAR LIMIT=40] -->
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 92f049d..5b2522f 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -318,6 +318,7 @@
         <item name="activityChooserViewStyle">@style/Widget.ActivityChooserView</item>
         <item name="mediaRouteButtonStyle">@style/Widget.DeviceDefault.MediaRouteButton</item>
         <item name="fragmentBreadCrumbsStyle">@style/Widget.FragmentBreadCrumbs</item>
+        <item name="contextPopupMenuStyle">?attr/popupMenuStyle</item>
 
         <!-- Preference styles -->
         <item name="preferenceScreenStyle">@style/Preference.PreferenceScreen</item>
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index bcc354c..3dbd2a9 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -699,7 +699,7 @@
             float rad = mStrokePaint.getStrokeWidth();
             canvas.saveLayer(mRect.left - rad, mRect.top - rad,
                              mRect.right + rad, mRect.bottom + rad,
-                             mLayerPaint, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);
+                             mLayerPaint);
 
             // don't perform the filter in our individual paints
             // since the layer will do it for us
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index a8c8737..c34f474 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -243,7 +243,9 @@
      * constructors to set the state and initialize local properties.
      */
     private VectorDrawable(@NonNull VectorDrawableState state, @Nullable Resources res) {
-        mVectorState = state;
+        // Constant state sharing is disabled until we fix onStateChanged()
+        // affecting the shared bitmap.
+        mVectorState = new VectorDrawableState(state);
         updateLocalState(res);
     }
 
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index b35c926..cbefccb 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -127,7 +127,8 @@
     // operations will be able to store and restore the current clip and transform info, and
     // quick rejection will be correct (for display lists)
 
-    const Rect unmappedBounds(left, top, right, bottom);
+    Rect unmappedBounds(left, top, right, bottom);
+    unmappedBounds.roundOut();
 
     // determine clipped bounds relative to previous viewport.
     Rect visibleBounds = unmappedBounds;
diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
index 18171de..9cd504e 100644
--- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp
+++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
@@ -340,6 +340,36 @@
     EXPECT_EQ(3, count);
 }
 
+TEST(RecordingCanvas, saveLayer_rounding) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) {
+            canvas.saveLayerAlpha(10.25f, 10.75f, 89.25f, 89.75f, 128, SaveFlags::ClipToLayer);
+            canvas.drawRect(20, 20, 80, 80, SkPaint());
+            canvas.restore();
+        });
+        int count = 0;
+        playbackOps(*dl, [&count](const RecordedOp& op) {
+            Matrix4 expectedMatrix;
+            switch(count++) {
+            case 0:
+                EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId);
+                EXPECT_EQ(Rect(10, 10, 90, 90), op.unmappedBounds) << "Expect bounds rounded out";
+                break;
+            case 1:
+                EXPECT_EQ(RecordedOpId::RectOp, op.opId);
+                expectedMatrix.loadTranslate(-10, -10, 0);
+                EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix) << "Expect rounded offset";
+                break;
+            case 2:
+                EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId);
+                // Don't bother asserting recording state data - it's not used
+                break;
+            default:
+                ADD_FAILURE();
+            }
+        });
+        EXPECT_EQ(3, count);
+}
+
 TEST(RecordingCanvas, saveLayer_missingRestore) {
     auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
         canvas.saveLayerAlpha(0, 0, 200, 200, 128, SaveFlags::ClipToLayer);
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 81ac594..542dced 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -613,6 +613,32 @@
  Also since {@link android.os.Build.VERSION_CODES#M}, you can change the output Surface
  dynamically using {@link #setOutputSurface setOutputSurface}.
 
+ <h4>Transformations When Rendering onto Surface</h4>
+
+ If the codec is configured into Surface mode, any crop rectangle, {@linkplain
+ MediaFormat#KEY_ROTATION rotation} and {@linkplain #setVideoScalingMode video scaling
+ mode} will be automatically applied with one exception:
+ <p class=note>
+ Prior to the {@link android.os.Build.VERSION_CODES#M} release, software decoders may not
+ have applied the rotation when being rendered onto a Surface. Unfortunately, there is no way to
+ identify software decoders, or if they apply the rotation other than by trying it out.
+ <p>
+ There are also some caveats.
+ <p class=note>
+ Note that the pixel aspect ratio is not considered when displaying the output onto the
+ Surface. This means that if you are using {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT} mode, you
+ must position the output Surface so that it has the proper final display aspect ratio. Conversely,
+ you can only use {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING} mode for content with
+ square pixels (pixel aspect ratio or 1:1).
+ <p class=note>
+ Note also that as of {@link android.os.Build.VERSION_CODES#N} release, {@link
+ #VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING} mode may not work correctly for videos rotated
+ by 90 or 270 degrees.
+ <p class=note>
+ When setting the video scaling mode, note that it must be reset after each time the output
+ buffers change. Since the {@link #INFO_OUTPUT_BUFFERS_CHANGED} event is deprecated, you can
+ do this after each time the output format changes.
+
  <h4>Using an Input Surface</h4>
  <p>
  When using an input Surface, there are no accessible input buffers, as buffers are automatically
@@ -3062,7 +3088,13 @@
 
     /**
      * The content is scaled, maintaining its aspect ratio, the whole
-     * surface area is used, content may be cropped
+     * surface area is used, content may be cropped.
+     * <p class=note>
+     * This mode is only suitable for content with 1:1 pixel aspect ratio as you cannot
+     * configure the pixel aspect ratio for a {@link Surface}.
+     * <p class=note>
+     * As of {@link android.os.Build.VERSION_CODES#N} release, this mode may not work if
+     * the video is {@linkplain MediaFormat#KEY_ROTATION rotated} by 90 or 270 degrees.
      */
     public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2;
 
@@ -3077,10 +3109,15 @@
     /**
      * If a surface has been specified in a previous call to {@link #configure}
      * specifies the scaling mode to use. The default is "scale to fit".
-     * <p class=note>The scaling mode may be reset to the <strong>default</strong> each time an
+     * <p class=note>
+     * The scaling mode may be reset to the <strong>default</strong> each time an
      * {@link #INFO_OUTPUT_BUFFERS_CHANGED} event is received from the codec; therefore, the client
      * must call this method after every buffer change event (and before the first output buffer is
-     * released for rendering) to ensure consistent scaling mode.</p>
+     * released for rendering) to ensure consistent scaling mode.
+     * <p class=note>
+     * Since the {@link #INFO_OUTPUT_BUFFERS_CHANGED} event is deprecated, this can also be done
+     * after each {@link #INFO_OUTPUT_FORMAT_CHANGED} event.
+     *
      * @throws IllegalArgumentException if mode is not recognized.
      * @throws IllegalStateException if in the Released state.
      */
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 07d1f75..0bfeaed 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -541,6 +541,72 @@
          * frame rate}. Use
          * <code class=prettyprint>format.setString(MediaFormat.KEY_FRAME_RATE, null)</code>
          * to clear any existing frame rate setting in the format.
+         * <p>
+         *
+         * The following table summarizes the format keys considered by this method.
+         *
+         * <table style="width: 0%">
+         *  <thead>
+         *   <tr>
+         *    <th rowspan=3>OS Version(s)</th>
+         *    <td colspan=3>{@code MediaFormat} keys considered for</th>
+         *   </tr><tr>
+         *    <th>Audio Codecs</th>
+         *    <th>Video Codecs</th>
+         *    <th>Encoders</th>
+         *   </tr>
+         *  </thead>
+         *  <tbody>
+         *   <tr>
+         *    <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP}</th>
+         *    <td rowspan=3>{@link MediaFormat#KEY_MIME}<sup>*</sup>,<br>
+         *        {@link MediaFormat#KEY_SAMPLE_RATE},<br>
+         *        {@link MediaFormat#KEY_CHANNEL_COUNT},</td>
+         *    <td>{@link MediaFormat#KEY_MIME}<sup>*</sup>,<br>
+         *        {@link CodecCapabilities#FEATURE_AdaptivePlayback}<sup>D</sup>,<br>
+         *        {@link CodecCapabilities#FEATURE_SecurePlayback}<sup>D</sup>,<br>
+         *        {@link CodecCapabilities#FEATURE_TunneledPlayback}<sup>D</sup>,<br>
+         *        {@link MediaFormat#KEY_WIDTH},<br>
+         *        {@link MediaFormat#KEY_HEIGHT},<br>
+         *        <strong>no</strong> {@code KEY_FRAME_RATE}</td>
+         *    <td rowspan=4>{@link MediaFormat#KEY_BITRATE_MODE},<br>
+         *        {@link MediaFormat#KEY_PROFILE}
+         *        (and/or {@link MediaFormat#KEY_AAC_PROFILE}<sup>~</sup>),<br>
+         *        <!-- {link MediaFormat#KEY_QUALITY},<br> -->
+         *        {@link MediaFormat#KEY_COMPLEXITY}
+         *        (and/or {@link MediaFormat#KEY_FLAC_COMPRESSION_LEVEL}<sup>~</sup>)</td>
+         *   </tr><tr>
+         *    <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}</th>
+         *    <td rowspan=2>as above, plus<br>
+         *        {@link MediaFormat#KEY_FRAME_RATE}</td>
+         *   </tr><tr>
+         *    <td>{@link android.os.Build.VERSION_CODES#M}</th>
+         *   </tr><tr>
+         *    <td>{@link android.os.Build.VERSION_CODES#N}</th>
+         *    <td>as above, plus<br>
+         *        {@link MediaFormat#KEY_PROFILE},<br>
+         *        <!-- {link MediaFormat#KEY_MAX_BIT_RATE},<br> -->
+         *        {@link MediaFormat#KEY_BIT_RATE}</td>
+         *    <td>as above, plus<br>
+         *        {@link MediaFormat#KEY_PROFILE},<br>
+         *        {@link MediaFormat#KEY_LEVEL}<sup>+</sup>,<br>
+         *        <!-- {link MediaFormat#KEY_MAX_BIT_RATE},<br> -->
+         *        {@link MediaFormat#KEY_BIT_RATE},<br>
+         *        {@link CodecCapabilities#FEATURE_IntraRefresh}<sup>E</sup></td>
+         *   </tr>
+         *   <tr>
+         *    <td colspan=4>
+         *     <p class=note><strong>Notes:</strong><br>
+         *      *: must be specified; otherwise, method returns {@code false}.<br>
+         *      +: method does not verify that the format parameters are supported
+         *      by the specified level.<br>
+         *      D: decoders only<br>
+         *      E: encoders only<br>
+         *      ~: if both keys are provided values must match
+         *    </td>
+         *   </tr>
+         *  </tbody>
+         * </table>
          *
          * @param format media format with optional feature directives.
          * @throws IllegalArgumentException if format is not a valid media format.
diff --git a/media/java/android/media/MediaCodecList.java b/media/java/android/media/MediaCodecList.java
index 42ce511..3cb4cbe 100644
--- a/media/java/android/media/MediaCodecList.java
+++ b/media/java/android/media/MediaCodecList.java
@@ -201,6 +201,9 @@
      * <code class=prettyprint>format.setString(MediaFormat.KEY_FRAME_RATE, null)</code>
      * to clear any existing frame rate setting in the format.
      *
+     * @see MediaCodecList.CodecCapabilities.isFormatSupported for format keys
+     * considered per android versions when evaluating suitable codecs.
+     *
      * @param format A decoder media format with optional feature directives.
      * @throws IllegalArgumentException if format is not a valid media format.
      * @throws NullPointerException if format is null.
@@ -222,6 +225,9 @@
      * <code class=prettyprint>format.setString(MediaFormat.KEY_FRAME_RATE, null)</code>
      * to clear any existing frame rate setting in the format.
      *
+     * @see MediaCodecList.CodecCapabilities.isFormatSupported for format keys
+     * considered per android versions when evaluating suitable codecs.
+     *
      * @param format An encoder media format with optional feature directives.
      * @throws IllegalArgumentException if format is not a valid media format.
      * @throws NullPointerException if format is null.
diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java
index 24a400e4..6f5199b 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -333,7 +333,113 @@
 
     /**
      * Get the track format at the specified index.
+     *
      * More detail on the representation can be found at {@link android.media.MediaCodec}
+     * <p>
+     * The following table summarizes support for format keys across android releases:
+     *
+     * <table style="width: 0%">
+     *  <thead>
+     *   <tr>
+     *    <th rowspan=2>OS Version(s)</th>
+     *    <td colspan=3>{@code MediaFormat} keys used for</th>
+     *   </tr><tr>
+     *    <th>All Tracks</th>
+     *    <th>Audio Tracks</th>
+     *    <th>Video Tracks</th>
+     *   </tr>
+     *  </thead>
+     *  <tbody>
+     *   <tr>
+     *    <td>{@link android.os.Build.VERSION_CODES#JELLY_BEAN}</td>
+     *    <td rowspan=8>{@link MediaFormat#KEY_MIME},<br>
+     *        {@link MediaFormat#KEY_DURATION},<br>
+     *        {@link MediaFormat#KEY_MAX_INPUT_SIZE}</td>
+     *    <td rowspan=5>{@link MediaFormat#KEY_SAMPLE_RATE},<br>
+     *        {@link MediaFormat#KEY_CHANNEL_COUNT},<br>
+     *        {@link MediaFormat#KEY_CHANNEL_MASK},<br>
+     *        gapless playback information<sup>.mp3, .mp4</sup>,<br>
+     *        {@link MediaFormat#KEY_IS_ADTS}<sup>AAC if streaming</sup>,<br>
+     *        codec-specific data<sup>AAC, Vorbis</sup></td>
+     *    <td rowspan=2>{@link MediaFormat#KEY_WIDTH},<br>
+     *        {@link MediaFormat#KEY_HEIGHT},<br>
+     *        codec-specific data<sup>AVC, MPEG4</sup></td>
+     *   </tr><tr>
+     *    <td>{@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}</td>
+     *   </tr><tr>
+     *    <td>{@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}</td>
+     *    <td rowspan=3>as above, plus<br>
+     *        Pixel aspect ratio information<sup>AVC, *</sup></td>
+     *   </tr><tr>
+     *    <td>{@link android.os.Build.VERSION_CODES#KITKAT}</td>
+     *   </tr><tr>
+     *    <td>{@link android.os.Build.VERSION_CODES#KITKAT_WATCH}</td>
+     *   </tr><tr>
+     *    <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP}</td>
+     *    <td rowspan=2>as above, plus<br>
+     *        {@link MediaFormat#KEY_BIT_RATE}<sup>AAC</sup>,<br>
+     *        codec-specific data<sup>Opus</sup></td>
+     *    <td rowspan=2>as above, plus<br>
+     *        {@link MediaFormat#KEY_ROTATION}<sup>.mp4</sup>,<br>
+     *        {@link MediaFormat#KEY_BIT_RATE}<sup>MPEG4</sup>,<br>
+     *        codec-specific data<sup>HEVC</sup></td>
+     *   </tr><tr>
+     *    <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}</td>
+     *   </tr><tr>
+     *    <td>{@link android.os.Build.VERSION_CODES#M}</td>
+     *    <td>as above, plus<br>
+     *        gapless playback information<sup>Opus</sup></td>
+     *    <td>as above, plus<br>
+     *        {@link MediaFormat#KEY_FRAME_RATE} (integer)</td>
+     *   </tr><tr>
+     *    <td>{@link android.os.Build.VERSION_CODES#N}</td>
+     *    <td>as above, plus<br>
+     *        {@link MediaFormat#KEY_TRACK_ID},<br>
+     *        <!-- {link MediaFormat#KEY_MAX_BIT_RATE}<sup>#, .mp4</sup>,<br> -->
+     *        {@link MediaFormat#KEY_BIT_RATE}<sup>#, .mp4</sup></td>
+     *    <td>as above, plus<br>
+     *        {@link MediaFormat#KEY_PCM_ENCODING},<br>
+     *        {@link MediaFormat#KEY_PROFILE}<sup>AAC</sup></td>
+     *    <td>as above, plus<br>
+     *        {@link MediaFormat#KEY_HDR_STATIC_INFO}<sup>#, .webm</sup>,<br>
+     *        {@link MediaFormat#KEY_COLOR_STANDARD}<sup>#</sup>,<br>
+     *        {@link MediaFormat#KEY_COLOR_TRANSFER}<sup>#</sup>,<br>
+     *        {@link MediaFormat#KEY_COLOR_RANGE}<sup>#</sup>,<br>
+     *        {@link MediaFormat#KEY_PROFILE}<sup>MPEG2, H.263, MPEG4, AVC, HEVC, VP9</sup>,<br>
+     *        {@link MediaFormat#KEY_LEVEL}<sup>H.263, MPEG4, AVC, HEVC, VP9</sup>,<br>
+     *        codec-specific data<sup>VP9</sup></td>
+     *   </tr>
+     *   <tr>
+     *    <td colspan=4>
+     *     <p class=note><strong>Notes:</strong><br>
+     *      #: container-specified value only.<br>
+     *      .mp4, .webm&hellip;: for listed containers<br>
+     *      MPEG4, AAC&hellip;: for listed codecs
+     *    </td>
+     *   </tr><tr>
+     *    <td colspan=4>
+     *     <p class=note>Note that that level information contained in the container many times
+     *     does not match the level of the actual bitstream. You may want to clear the level using
+     *     {@code MediaFormat.setString(KEY_LEVEL, null)} before using the track format to find a
+     *     decoder that can play back a particular track.
+     *    </td>
+     *   </tr><tr>
+     *    <td colspan=4>
+     *     <p class=note><strong>*Pixel (sample) aspect ratio</strong> is returned in the following
+     *     keys. The display width can be calculated for example as:
+     *     <p align=center>
+     *     display-width = display-height * crop-width / crop-height * sar-width / sar-height
+     *    </td>
+     *   </tr><tr>
+     *    <th>Format Key</th><th>Value Type</th><th colspan=2>Description</th>
+     *   </tr><tr>
+     *    <td>{@code "sar-width"}</td><td>Integer</td><td colspan=2>Pixel aspect ratio width</td>
+     *   </tr><tr>
+     *    <td>{@code "sar-height"}</td><td>Integer</td><td colspan=2>Pixel aspect ratio height</td>
+     *   </tr>
+     *  </tbody>
+     * </table>
+     *
      */
     @NonNull
     public MediaFormat getTrackFormat(int index) {
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index a2fd0aa..d7a18d9 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -554,7 +554,9 @@
     /**
      * A key describing the desired clockwise rotation on an output surface.
      * This key is only used when the codec is configured using an output surface.
-     * The associated value is an integer, representing degrees.
+     * The associated value is an integer, representing degrees. Supported values
+     * are 0, 90, 180 or 270. This is an optional field; if not specified, rotation
+     * defaults to 0.
      *
      * @see MediaCodecInfo.CodecCapabilities#profileLevels
      */
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 7993131..548ddf8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -16,6 +16,7 @@
 import android.net.ConnectivityManager;
 import android.os.BatteryManager;
 import android.os.UserManager;
+import android.print.PrintManager;
 import com.android.internal.util.UserIcons;
 import com.android.settingslib.drawable.UserIconDrawable;
 
@@ -185,7 +186,8 @@
                         && sSystemSignature[0].equals(getFirstSignature(pkg)))
                 || pkg.packageName.equals(sPermissionControllerPackageName)
                 || pkg.packageName.equals(sServicesSystemSharedLibPackageName)
-                || pkg.packageName.equals(sSharedSystemSharedLibPackageName);
+                || pkg.packageName.equals(sSharedSystemSharedLibPackageName)
+                || pkg.packageName.equals(PrintManager.PRINT_SPOOLER_PACKAGE_NAME);
     }
 
     private static Signature getFirstSignature(PackageInfo pkg) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index b20f46f..7e1deec 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -184,7 +184,9 @@
         mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
                 null, false);
         reloadResources();
+    }
 
+    public void onBootCompleted() {
         // When we start, preload the data associated with the previous recent tasks.
         // We can use a new plan since the caches will be the same.
         RecentsTaskLoader loader = Recents.getTaskLoader();
@@ -197,10 +199,6 @@
         loader.loadTasks(mContext, plan, launchOpts);
     }
 
-    public void onBootCompleted() {
-        // Do nothing
-    }
-
     public void onConfigurationChanged() {
         reloadResources();
         mDummyStackView.reloadOnConfigurationChange();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 94231c6..37a4948 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -314,8 +314,12 @@
         if (includeFrontMostExcludedTask) {
             flags |= ActivityManager.RECENT_WITH_EXCLUDED;
         }
-        List<ActivityManager.RecentTaskInfo> tasks = mAm.getRecentTasksForUser(numTasksToQuery,
-                flags, userId);
+        List<ActivityManager.RecentTaskInfo> tasks = null;
+        try {
+            tasks = mAm.getRecentTasksForUser(numTasksToQuery, flags, userId);
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to get recent tasks", e);
+        }
 
         // Break early if we can't get a valid set of tasks
         if (tasks == null) {
@@ -330,8 +334,9 @@
             // NOTE: The order of these checks happens in the expected order of the traversal of the
             // tasks
 
-            // Remove the task if it is blacklisted
-            if (sRecentsBlacklist.contains(t.realActivity.getClassName())) {
+            // Remove the task if it or it's package are blacklsited
+            if (sRecentsBlacklist.contains(t.realActivity.getClassName()) ||
+                    sRecentsBlacklist.contains(t.realActivity.getPackageName())) {
                 iter.remove();
                 continue;
             }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 44b8d3d..1a7a2bf 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -76,6 +76,7 @@
 import android.net.UidRange;
 import android.net.Uri;
 import android.net.metrics.DefaultNetworkEvent;
+import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.NetworkEvent;
 import android.os.Binder;
 import android.os.Build;
@@ -454,6 +455,8 @@
         }
     }
 
+    private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
+
     /**
      * Implements support for the legacy "one network per network type" model.
      *
@@ -2205,7 +2208,7 @@
 
     private void linger(NetworkAgentInfo nai) {
         nai.lingering = true;
-        NetworkEvent.logEvent(nai.network.netId, NetworkEvent.NETWORK_LINGER);
+        logNetworkEvent(nai, NetworkEvent.NETWORK_LINGER);
         nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_LINGER);
         notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING);
     }
@@ -2219,7 +2222,7 @@
         nai.networkLingered.clear();
         if (!nai.lingering) return;
         nai.lingering = false;
-        NetworkEvent.logEvent(nai.network.netId, NetworkEvent.NETWORK_UNLINGER);
+        logNetworkEvent(nai, NetworkEvent.NETWORK_UNLINGER);
         if (VDBG) log("Canceling linger of " + nai.name());
         nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
     }
@@ -5242,7 +5245,7 @@
         return new NetworkMonitor(context, handler, nai, defaultRequest);
     }
 
-    private static void logDefaultNetworkEvent(NetworkAgentInfo newNai, NetworkAgentInfo prevNai) {
+    private void logDefaultNetworkEvent(NetworkAgentInfo newNai, NetworkAgentInfo prevNai) {
         int newNetid = NETID_UNSET;
         int prevNetid = NETID_UNSET;
         int[] transports = new int[0];
@@ -5260,6 +5263,10 @@
             hadIPv6 = lp.hasGlobalIPv6Address() && lp.hasIPv6DefaultRoute();
         }
 
-        DefaultNetworkEvent.logEvent(newNetid, transports, prevNetid, hadIPv4, hadIPv6);
+        mMetricsLog.log(new DefaultNetworkEvent(newNetid, transports, prevNetid, hadIPv4, hadIPv6));
+    }
+
+    private void logNetworkEvent(NetworkAgentInfo nai, int evtype) {
+        mMetricsLog.log(new NetworkEvent(nai.network.netId, evtype));
     }
 }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index d914881..ee2fa51 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1148,9 +1148,7 @@
 
                 if (r.binding.service.app != null) {
                     if (r.binding.service.app.whitelistManager) {
-                        // Must reset flag here because on computeOomAdjLocked() the service
-                        // connection will be gone...
-                        r.binding.service.app.whitelistManager = false;
+                        updateWhitelistManagerLocked(r.binding.service.app);
                     }
                     // This could have made the service less important.
                     if ((r.flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
diff --git a/services/core/java/com/android/server/am/PreBootBroadcaster.java b/services/core/java/com/android/server/am/PreBootBroadcaster.java
index 1f3ccf5..3ed3d9a 100644
--- a/services/core/java/com/android/server/am/PreBootBroadcaster.java
+++ b/services/core/java/com/android/server/am/PreBootBroadcaster.java
@@ -19,17 +19,23 @@
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 
 import android.app.AppOpsManager;
+import android.app.Notification;
+import android.app.NotificationManager;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.IIntentReceiver;
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
 import android.os.Process;
 import android.os.UserHandle;
 import android.util.Slog;
 
 import com.android.internal.R;
 import com.android.internal.util.ProgressReporter;
+import com.android.server.UiThread;
 
 import java.util.List;
 
@@ -61,16 +67,20 @@
 
         mTargets = mService.mContext.getPackageManager().queryBroadcastReceiversAsUser(mIntent,
                 MATCH_SYSTEM_ONLY, UserHandle.of(userId));
+
+        mHandler.obtainMessage(MSG_SHOW).sendToTarget();
     }
 
     public void sendNext() {
         if (mIndex >= mTargets.size()) {
+            mHandler.obtainMessage(MSG_HIDE).sendToTarget();
             onFinished();
             return;
         }
 
         if (!mService.isUserRunning(mUserId, 0)) {
             Slog.i(TAG, "User " + mUserId + " is no longer running; skipping remaining receivers");
+            mHandler.obtainMessage(MSG_HIDE).sendToTarget();
             onFinished();
             return;
         }
@@ -100,5 +110,44 @@
         sendNext();
     }
 
+    private static final int MSG_SHOW = 1;
+    private static final int MSG_HIDE = 2;
+
+    private Handler mHandler = new Handler(UiThread.get().getLooper(), null, true) {
+        @Override
+        public void handleMessage(Message msg) {
+            final Context context = mService.mContext;
+            final NotificationManager notifManager = context
+                    .getSystemService(NotificationManager.class);
+
+            switch (msg.what) {
+                case MSG_SHOW:
+                    final CharSequence title = context
+                            .getText(R.string.android_upgrading_notification_title);
+                    final CharSequence message = context
+                            .getText(R.string.android_upgrading_notification_body);
+                    final Notification notif = new Notification.Builder(mService.mContext)
+                            .setSmallIcon(R.drawable.stat_sys_adb)
+                            .setWhen(0)
+                            .setOngoing(true)
+                            .setTicker(title)
+                            .setDefaults(0)
+                            .setPriority(Notification.PRIORITY_MAX)
+                            .setColor(context.getColor(
+                                    com.android.internal.R.color.system_notification_accent_color))
+                            .setContentTitle(title)
+                            .setContentText(message)
+                            .setVisibility(Notification.VISIBILITY_PUBLIC)
+                            .build();
+                    notifManager.notifyAsUser(TAG, 0, notif, UserHandle.of(mUserId));
+                    break;
+
+                case MSG_HIDE:
+                    notifManager.cancelAsUser(TAG, 0, UserHandle.of(mUserId));
+                    break;
+            }
+        }
+    };
+
     public abstract void onFinished();
 }
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 6b44f14..d25f2cb 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -99,6 +99,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Helper class for {@link ActivityManagerService} responsible for multi-user functionality.
@@ -1057,6 +1058,7 @@
                 uss.switching = true;
                 mCurWaitingUserSwitchCallbacks = curWaitingUserSwitchCallbacks;
             }
+            final AtomicInteger waitingCallbacksCount = new AtomicInteger(observerCount);
             for (int i = 0; i < observerCount; i++) {
                 try {
                     // Prepend with unique prefix to guarantee that keys are unique
@@ -1075,7 +1077,7 @@
                                 }
                                 curWaitingUserSwitchCallbacks.remove(name);
                                 // Continue switching if all callbacks have been notified
-                                if (curWaitingUserSwitchCallbacks.isEmpty()) {
+                                if (waitingCallbacksCount.decrementAndGet() == 0) {
                                     sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
                                 }
                             }
diff --git a/services/core/java/com/android/server/connectivity/DnsEventListenerService.java b/services/core/java/com/android/server/connectivity/DnsEventListenerService.java
index 18ab731..8d206ef 100644
--- a/services/core/java/com/android/server/connectivity/DnsEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/DnsEventListenerService.java
@@ -17,15 +17,17 @@
 package com.android.server.connectivity;
 
 import android.content.Context;
-import android.net.metrics.DnsEvent;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.Network;
 import android.net.NetworkRequest;
+import android.net.metrics.DnsEvent;
 import android.net.metrics.IDnsEventListener;
+import android.net.metrics.IpConnectivityLog;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 
 import java.io.PrintWriter;
@@ -45,12 +47,13 @@
     private static final boolean DBG = true;
     private static final boolean VDBG = false;
 
+    // TODO: read this constant from system property
     private static final int MAX_LOOKUPS_PER_DNS_EVENT = 100;
 
     // Stores the results of a number of consecutive DNS lookups on the same network.
     // This class is not thread-safe and it is the responsibility of the service to call its methods
     // on one thread at a time.
-    private static class DnsEventBatch {
+    private class DnsEventBatch {
         private final int mNetId;
 
         private final byte[] mEventTypes = new byte[MAX_LOOKUPS_PER_DNS_EVENT];
@@ -82,7 +85,7 @@
             byte[] eventTypes = Arrays.copyOf(mEventTypes, mEventCount);
             byte[] returnCodes = Arrays.copyOf(mReturnCodes, mEventCount);
             int[] latenciesMs = Arrays.copyOf(mLatenciesMs, mEventCount);
-            DnsEvent.logEvent(mNetId, eventTypes, returnCodes, latenciesMs);
+            mMetricsLog.log(new DnsEvent(mNetId, eventTypes, returnCodes, latenciesMs));
             maybeLog(String.format("Logging %d results for netId %d", mEventCount, mNetId));
             mEventCount = 0;
         }
@@ -96,13 +99,14 @@
     // Only sorted for ease of debugging. Because we only typically have a handful of networks up
     // at any given time, performance is not a concern.
     @GuardedBy("this")
-    private SortedMap<Integer, DnsEventBatch> mEventBatches = new TreeMap<>();
+    private final SortedMap<Integer, DnsEventBatch> mEventBatches = new TreeMap<>();
 
     // We register a NetworkCallback to ensure that when a network disconnects, we flush the DNS
     // queries we've logged on that network. Because we do not do this periodically, we might lose
     // up to MAX_LOOKUPS_PER_DNS_EVENT lookup stats on each network when the system is shutting
     // down. We believe this to be sufficient for now.
     private final ConnectivityManager mCm;
+    private final IpConnectivityLog mMetricsLog;
     private final NetworkCallback mNetworkCallback = new NetworkCallback() {
         @Override
         public void onLost(Network network) {
@@ -116,11 +120,15 @@
     };
 
     public DnsEventListenerService(Context context) {
+        this(context.getSystemService(ConnectivityManager.class), new IpConnectivityLog());
+    }
+
+    @VisibleForTesting
+    public DnsEventListenerService(ConnectivityManager cm, IpConnectivityLog log) {
         // We are started when boot is complete, so ConnectivityService should already be running.
-        final NetworkRequest request = new NetworkRequest.Builder()
-            .clearCapabilities()
-            .build();
-        mCm = context.getSystemService(ConnectivityManager.class);
+        mCm = cm;
+        mMetricsLog = log;
+        final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
         mCm.registerNetworkCallback(request, mNetworkCallback);
     }
 
diff --git a/services/core/java/com/android/server/connectivity/MetricsLoggerService.java b/services/core/java/com/android/server/connectivity/MetricsLoggerService.java
index 69ef30f..05f1a6e 100644
--- a/services/core/java/com/android/server/connectivity/MetricsLoggerService.java
+++ b/services/core/java/com/android/server/connectivity/MetricsLoggerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.connectivity;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.SystemService;
 
 import android.app.PendingIntent;
@@ -60,17 +61,11 @@
         }
     }
 
-    // TODO: read from system property
-    private final int MAX_NUMBER_OF_EVENTS = 1000;
-
-    // TODO: read from system property
-    private final int EVENTS_NOTIFICATION_THRESHOLD = 300;
-
-    // TODO: read from system property
-    private final int THROTTLING_TIME_INTERVAL_MILLIS = 60 * 60 * 1000; // 1 hour
-
-    // TODO: read from system property
+    // TODO: read these constants from system property
+    private final int EVENTS_NOTIFICATION_THRESHOLD                   = 300;
+    private final int MAX_NUMBER_OF_EVENTS                            = 1000;
     private final int THROTTLING_MAX_NUMBER_OF_MESSAGES_PER_COMPONENT = 1000;
+    private final long THROTTLING_TIME_INTERVAL_MILLIS                = DateUtils.HOUR_IN_MILLIS;
 
     private int mEventCounter = 0;
 
@@ -127,10 +122,13 @@
         mEvents.addLast(e);
     }
 
+    @VisibleForTesting
+    final MetricsLoggerImpl mBinder = new MetricsLoggerImpl();
+
     /**
      * Implementation of the IConnectivityMetricsLogger interface.
      */
-    private final IConnectivityMetricsLogger.Stub mBinder = new IConnectivityMetricsLogger.Stub() {
+    final class MetricsLoggerImpl extends IConnectivityMetricsLogger.Stub {
 
         private final ArrayList<PendingIntent> mPendingIntents = new ArrayList<>();
 
@@ -223,7 +221,9 @@
             }
 
             pw.println();
-            mDnsListener.dump(pw);
+            if (mDnsListener != null) {
+                mDnsListener.dump(pw);
+            }
         }
 
         public long logEvent(ConnectivityMetricsEvent event) {
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index ddaebfa..eeddff5 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -34,8 +34,9 @@
 import android.net.ProxyInfo;
 import android.net.TrafficStats;
 import android.net.Uri;
-import android.net.metrics.ValidationProbeEvent;
+import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.NetworkEvent;
+import android.net.metrics.ValidationProbeEvent;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.net.util.Stopwatch;
@@ -230,6 +231,7 @@
     private final WifiManager mWifiManager;
     private final AlarmManager mAlarmManager;
     private final NetworkRequest mDefaultRequest;
+    private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
 
     private boolean mIsCaptivePortalCheckEnabled;
     private boolean mUseHttps;
@@ -311,11 +313,11 @@
                     transitionTo(mLingeringState);
                     return HANDLED;
                 case CMD_NETWORK_CONNECTED:
-                    NetworkEvent.logEvent(mNetId, NetworkEvent.NETWORK_CONNECTED);
+                    logNetworkEvent(NetworkEvent.NETWORK_CONNECTED);
                     transitionTo(mEvaluatingState);
                     return HANDLED;
                 case CMD_NETWORK_DISCONNECTED:
-                    NetworkEvent.logEvent(mNetId, NetworkEvent.NETWORK_DISCONNECTED);
+                    logNetworkEvent(NetworkEvent.NETWORK_DISCONNECTED);
                     if (mLaunchCaptivePortalAppBroadcastReceiver != null) {
                         mContext.unregisterReceiver(mLaunchCaptivePortalAppBroadcastReceiver);
                         mLaunchCaptivePortalAppBroadcastReceiver = null;
@@ -380,10 +382,7 @@
     private class ValidatedState extends State {
         @Override
         public void enter() {
-            if (mEvaluationTimer.isRunning()) {
-                NetworkEvent.logValidated(mNetId, mEvaluationTimer.stop());
-                mEvaluationTimer.reset();
-            }
+            maybeLogEvaluationResult(NetworkEvent.NETWORK_VALIDATED);
             mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
                     NETWORK_TEST_RESULT_VALID, mNetworkAgentInfo.network.netId, null));
         }
@@ -530,7 +529,7 @@
                     } else {
                         final Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
                         sendMessageDelayed(msg, mReevaluateDelayMs);
-                        NetworkEvent.logEvent(mNetId, NetworkEvent.NETWORK_VALIDATION_FAILED);
+                        logNetworkEvent(NetworkEvent.NETWORK_VALIDATION_FAILED);
                         mConnectivityServiceHandler.sendMessage(obtainMessage(
                                 EVENT_NETWORK_TESTED, NETWORK_TEST_RESULT_INVALID, mNetId,
                                 probeResult.mRedirectUrl));
@@ -590,10 +589,7 @@
 
         @Override
         public void enter() {
-            if (mEvaluationTimer.isRunning()) {
-                NetworkEvent.logCaptivePortalFound(mNetId, mEvaluationTimer.stop());
-                mEvaluationTimer.reset();
-            }
+            maybeLogEvaluationResult(NetworkEvent.NETWORK_CAPTIVE_PORTAL_FOUND);
             // Don't annoy user with sign-in notifications.
             if (mDontDisplaySigninNotification) return;
             // Create a CustomIntentReceiver that sends us a
@@ -758,11 +754,12 @@
         if (!TextUtils.isEmpty(hostToResolve)) {
             String probeName = ValidationProbeEvent.getProbeName(ValidationProbeEvent.PROBE_DNS);
             final Stopwatch dnsTimer = new Stopwatch().start();
+            int dnsResult;
+            long dnsLatency;
             try {
                 InetAddress[] addresses = mNetworkAgentInfo.network.getAllByName(hostToResolve);
-                long dnsLatency = dnsTimer.stop();
-                ValidationProbeEvent.logEvent(mNetId, dnsLatency,
-                        ValidationProbeEvent.PROBE_DNS, ValidationProbeEvent.DNS_SUCCESS);
+                dnsResult = ValidationProbeEvent.DNS_SUCCESS;
+                dnsLatency = dnsTimer.stop();
                 final StringBuffer connectInfo = new StringBuffer(", " + hostToResolve + "=");
                 for (InetAddress address : addresses) {
                     connectInfo.append(address.getHostAddress());
@@ -770,11 +767,11 @@
                 }
                 validationLog(probeName + " OK " + dnsLatency + "ms" + connectInfo);
             } catch (UnknownHostException e) {
-                long dnsLatency = dnsTimer.stop();
-                ValidationProbeEvent.logEvent(mNetId, dnsLatency,
-                        ValidationProbeEvent.PROBE_DNS, ValidationProbeEvent.DNS_FAILURE);
+                dnsResult = ValidationProbeEvent.DNS_FAILURE;
+                dnsLatency = dnsTimer.stop();
                 validationLog(probeName + " FAIL " + dnsLatency + "ms, " + hostToResolve);
             }
+            logValidationProbe(dnsLatency, ValidationProbeEvent.PROBE_DNS, dnsResult);
         }
 
         CaptivePortalProbeResult result;
@@ -855,7 +852,7 @@
                 urlConnection.disconnect();
             }
         }
-        ValidationProbeEvent.logEvent(mNetId, probeTimer.stop(), probeType, httpResponseCode);
+        logValidationProbe(probeTimer.stop(), probeType, httpResponseCode);
         return new CaptivePortalProbeResult(httpResponseCode, redirectUrl);
     }
 
@@ -1012,4 +1009,19 @@
     protected WakeupMessage makeWakeupMessage(Context c, Handler h, String s, int i) {
         return new WakeupMessage(c, h, s, i);
     }
+
+    private void logNetworkEvent(int evtype) {
+        mMetricsLog.log(new NetworkEvent(mNetId, evtype));
+    }
+
+    private void maybeLogEvaluationResult(int evtype) {
+        if (mEvaluationTimer.isRunning()) {
+            mMetricsLog.log(new NetworkEvent(mNetId, evtype, mEvaluationTimer.stop()));
+            mEvaluationTimer.reset();
+        }
+    }
+
+    private void logValidationProbe(long durationMs, int probeType, int probeResult) {
+        mMetricsLog.log(new ValidationProbeEvent(mNetId, durationMs, probeType, probeResult));
+    }
 }
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index 14e4bc6..be8e300 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -1019,14 +1019,12 @@
         }
     }
 
+    /***
+     * @param opPackageName the name of the calling package
+     * @return authenticator id for the current user
+     */
     public long getAuthenticatorId(String opPackageName) {
-        if (canUseFingerprint(opPackageName, false /* foregroundOnly */,
-                Binder.getCallingUid(), Binder.getCallingPid())) {
-            return mCurrentAuthenticatorId;
-        } else {
-            Slog.w(TAG, "Client isn't current, returning authenticator_id=0");
-        }
-        return 0;
+        return mCurrentAuthenticatorId;
     }
 
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 9209d3d..1206263 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1997,6 +1997,7 @@
                     android.Manifest.permission.MANAGE_NOTIFICATIONS)) {
                 return;
             }
+            checkCallerIsSameApp(pkg);
             if (!checkPolicyAccess(pkg)) {
                 Slog.w(TAG, "Notification policy access denied calling " + method);
                 throw new SecurityException("Notification policy access denied");
@@ -2577,7 +2578,22 @@
                     + " id=" + id + " notification=" + notification);
         }
 
-        markAsSentFromNotification(notification);
+        // Whitelist pending intents.
+        if (notification.allPendingIntents != null) {
+            final int intentCount = notification.allPendingIntents.size();
+            if (intentCount > 0) {
+                final ActivityManagerInternal am = LocalServices
+                        .getService(ActivityManagerInternal.class);
+                final long duration = LocalServices.getService(
+                        DeviceIdleController.LocalService.class).getNotificationWhitelistDuration();
+                for (int i = 0; i < intentCount; i++) {
+                    PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i);
+                    if (pendingIntent != null) {
+                        am.setPendingIntentWhitelistDuration(pendingIntent.getTarget(), duration);
+                    }
+                }
+            }
+        }
 
         // Sanitize inputs
         notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN,
@@ -2593,40 +2609,6 @@
         idOut[0] = id;
     }
 
-    private static void markAsSentFromNotification(Notification notification) {
-        final ActivityManagerInternal am = LocalServices.getService(ActivityManagerInternal.class);
-        final long duration = LocalServices.getService(DeviceIdleController.LocalService.class)
-                .getNotificationWhitelistDuration();
-
-        if (notification.contentIntent != null) {
-            am.setPendingIntentWhitelistDuration(notification.contentIntent.getTarget(), duration);
-        }
-        if (notification.deleteIntent != null) {
-            am.setPendingIntentWhitelistDuration(notification.deleteIntent.getTarget(), duration);
-        }
-        if (notification.fullScreenIntent != null) {
-            am.setPendingIntentWhitelistDuration(notification.fullScreenIntent.getTarget(),
-                    duration);
-        }
-        if (notification.actions != null) {
-            for (Notification.Action action: notification.actions) {
-                if (action.actionIntent == null) {
-                    continue;
-                }
-                am.setPendingIntentWhitelistDuration(action.actionIntent.getTarget(), duration);
-            }
-        }
-        if (notification.extrasPendingIntents != null) {
-            final int intentCount = notification.extrasPendingIntents.size();
-            for (int i = 0; i < intentCount; i++) {
-                PendingIntent pendingIntent = notification.extrasPendingIntents.valueAt(i);
-                if (pendingIntent != null) {
-                    am.setPendingIntentWhitelistDuration(pendingIntent.getTarget(), duration);
-                }
-            }
-        }
-    }
-
     private class EnqueueNotificationRunnable implements Runnable {
         private final NotificationRecord r;
         private final int userId;
@@ -3674,6 +3656,10 @@
         if (isCallerSystem()) {
             return;
         }
+        checkCallerIsSameApp(pkg);
+    }
+
+    private static void checkCallerIsSameApp(String pkg) {
         final int uid = Binder.getCallingUid();
         try {
             ApplicationInfo ai = AppGlobals.getPackageManager().getApplicationInfo(
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index d128252..bbffd32 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -456,7 +456,6 @@
             setUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, true, currentGuestUser.id);
         }
 
-        maybeInitializeDemoMode(UserHandle.USER_SYSTEM);
         mContext.registerReceiver(mDisableQuietModeCallback,
                 new IntentFilter(ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK),
                 null, mHandler);
@@ -869,12 +868,25 @@
             }
         }
         synchronized (mUsersLock) {
-            UserInfo userInfo =  getUserInfoLU(userId);
+            UserInfo userInfo = getUserInfoLU(userId);
             return userInfo != null && userInfo.isManagedProfile();
         }
     }
 
     @Override
+    public boolean isDemoUser(int userId) {
+        int callingUserId = UserHandle.getCallingUserId();
+        if (callingUserId != userId && !hasManageUsersPermission()) {
+            throw new SecurityException("You need MANAGE_USERS permission to query if u=" + userId
+                    + " is a demo user");
+        }
+        synchronized (mUsersLock) {
+            UserInfo userInfo = getUserInfoLU(userId);
+            return userInfo != null && userInfo.isDemo();
+        }
+    }
+
+    @Override
     public boolean isRestricted() {
         synchronized (mUsersLock) {
             return getUserInfoLU(UserHandle.getCallingUserId()).isRestricted();
@@ -2901,7 +2913,7 @@
     }
 
     private void maybeInitializeDemoMode(int userId) {
-        if (UserManager.isDeviceInDemoMode(mContext)) {
+        if (UserManager.isDeviceInDemoMode(mContext) && userId != UserHandle.USER_SYSTEM) {
             String demoLauncher =
                     mContext.getResources().getString(
                             com.android.internal.R.string.config_demoModeLauncherComponent);
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index edcc32e..b9cb38b 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -134,6 +134,9 @@
     boolean mAppStopped;
     int mPendingRelaunchCount;
 
+    private ArrayList<WindowSurfaceController.SurfaceControlWithBackground> mSurfaceViewBackgrounds =
+        new ArrayList<WindowSurfaceController.SurfaceControlWithBackground>();
+
     ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>();
     ArrayDeque<Configuration> mFrozenMergedConfig = new ArrayDeque<>();
 
@@ -734,6 +737,36 @@
         service.mWindowPlacerLocked.performSurfacePlacement();
     }
 
+    void addSurfaceViewBackground(WindowSurfaceController.SurfaceControlWithBackground background) {
+        mSurfaceViewBackgrounds.add(background);
+    }
+
+    void removeSurfaceViewBackground(WindowSurfaceController.SurfaceControlWithBackground background) {
+        mSurfaceViewBackgrounds.remove(background);
+        updateSurfaceViewBackgroundVisibilities();
+    }
+
+    // We use DimLayers behind SurfaceViews to prevent holes while resizing and creating.
+    // However, we need to ensure one SurfaceView doesn't cover another when they are both placed
+    // below the main app window (as traditionally a SurfaceView which is never drawn
+    // to is totally translucent). So we look at all our SurfaceView backgrounds and only enable
+    // the background for the SurfaceView with lowest Z order
+    void updateSurfaceViewBackgroundVisibilities() {
+        WindowSurfaceController.SurfaceControlWithBackground bottom = null;
+        int bottomLayer = Integer.MAX_VALUE;
+        for (int i = 0; i < mSurfaceViewBackgrounds.size(); i++) {
+            WindowSurfaceController.SurfaceControlWithBackground sc = mSurfaceViewBackgrounds.get(i);
+            if (sc.mVisible && sc.mLayer < bottomLayer) {
+                bottomLayer = sc.mLayer;
+                bottom = sc;
+            }
+        }
+        for (int i = 0; i < mSurfaceViewBackgrounds.size(); i++) {
+            WindowSurfaceController.SurfaceControlWithBackground sc = mSurfaceViewBackgrounds.get(i);
+            sc.updateBackgroundVisibility(sc != bottom);
+        }
+    }
+
     @Override
     void dump(PrintWriter pw, String prefix) {
         super.dump(pw, prefix);
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 88028be..18f97a7 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -479,6 +479,8 @@
         boolean resetTopWallpaper = false;
         boolean inFreeformSpace = false;
         boolean replacing = false;
+        boolean keyguardGoingAwayWithWallpaper = false;
+
         for (int i = windows.size() - 1; i >= 0; i--) {
             w = windows.get(i);
             if ((w.mAttrs.type == TYPE_WALLPAPER)) {
@@ -506,13 +508,11 @@
                 inFreeformSpace = stack != null && stack.mStackId == FREEFORM_WORKSPACE_STACK_ID;
             }
 
-            replacing = replacing || w.mWillReplaceWindow;
+            replacing |= w.mWillReplaceWindow;
+            keyguardGoingAwayWithWallpaper |= (w.mAppToken != null
+                    && w.mWinAnimator.mKeyguardGoingAwayWithWallpaper);
 
-            // If the app is executing an animation because the keyguard is going away (and the
-            // keyguard was showing the wallpaper) keep the wallpaper during the animation so it
-            // doesn't flicker out.
-            final boolean hasWallpaper = (w.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0
-                    || (w.mAppToken != null && w.mWinAnimator.mKeyguardGoingAwayWithWallpaper);
+            final boolean hasWallpaper = (w.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0;
             if (hasWallpaper && w.isOnScreen() && (mWallpaperTarget == w || w.isDrawFinishedLw())) {
                 if (DEBUG_WALLPAPER) Slog.v(TAG, "Found wallpaper target: #" + i + "=" + w);
                 result.setWallpaperTarget(w, i);
@@ -529,18 +529,26 @@
             }
         }
 
-        if (result.wallpaperTarget == null && windowDetachedI >= 0) {
+        if (result.wallpaperTarget != null) {
+            return;
+        }
+
+        if (windowDetachedI >= 0) {
             if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
                     "Found animating detached wallpaper activity: #" + windowDetachedI + "=" + w);
             result.setWallpaperTarget(w, windowDetachedI);
-        }
-        if (result.wallpaperTarget == null
-                && (inFreeformSpace || (replacing && mWallpaperTarget != null))) {
+        } else if (inFreeformSpace || (replacing && mWallpaperTarget != null)) {
             // In freeform mode we set the wallpaper as its own target, so we don't need an
             // additional window to make it visible. When we are replacing a window and there was
             // wallpaper before replacement, we want to keep the window until the new windows fully
             // appear and can determine the visibility, to avoid flickering.
             result.setWallpaperTarget(result.topWallpaper, result.topWallpaperIndex);
+
+        } else if (keyguardGoingAwayWithWallpaper) {
+            // If the app is executing an animation because the keyguard is going away (and the
+            // keyguard was showing the wallpaper) keep the wallpaper during the animation so it
+            // doesn't flicker out by having it be its own target.
+            result.setWallpaperTarget(result.topWallpaper, result.topWallpaperIndex);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b8accf5..84173d2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3003,8 +3003,8 @@
         if (win.mAttrs.type == TYPE_APPLICATION_STARTING) {
             transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
         }
-        if (win.isWinVisibleLw() && !winAnimator.isAnimationSet()
-                && winAnimator.applyAnimationLocked(transit, false)) {
+        if (win.isWinVisibleLw() && (winAnimator.isAnimationSet() ||
+                winAnimator.applyAnimationLocked(transit, false))) {
             focusMayChange = isDefaultDisplay;
             win.mAnimatingExit = true;
             win.mWinAnimator.mAnimating = true;
@@ -3170,6 +3170,7 @@
         // frozen, there is no reason to animate and it can cause strange
         // artifacts when we unfreeze the display if some different animation
         // is running.
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WM#applyAnimationLocked");
         if (okToDisplay()) {
             DisplayInfo displayInfo = getDefaultDisplayInfoLocked();
             final int width = displayInfo.appWidth;
@@ -3221,6 +3222,7 @@
         } else {
             atoken.mAppAnimator.clearAnimation();
         }
+        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
 
         return atoken.mAppAnimator.animation != null;
     }
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 6c55495..e374ee9 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -55,6 +55,7 @@
 import android.graphics.Region;
 import android.os.Debug;
 import android.os.RemoteException;
+import android.os.Trace;
 import android.util.Slog;
 import android.view.DisplayInfo;
 import android.view.MagnificationSpec;
@@ -1863,6 +1864,7 @@
         // frozen, there is no reason to animate and it can cause strange
         // artifacts when we unfreeze the display if some different animation
         // is running.
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WSA#applyAnimationLocked");
         if (mService.okToDisplay()) {
             int anim = mPolicy.selectAnimationLw(mWin, transit);
             int attr = -1;
@@ -1902,6 +1904,8 @@
         } else {
             clearAnimation();
         }
+        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+
         if (mWin.mAttrs.type == TYPE_INPUT_METHOD) {
             mService.adjustForImeIfNeeded(mWin.mDisplayContent);
             if (isEntrance) {
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index 3121415..c77e572 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -84,9 +84,10 @@
         // to a black-out layer placed one Z-layer below the surface.
         // This prevents holes to whatever app/wallpaper is underneath.
         if (animator.mWin.isChildWindow() &&
-                animator.mWin.mSubLayer < 0) {
+                animator.mWin.mSubLayer < 0 &&
+                animator.mWin.mAppToken != null) {
             mSurfaceControl = new SurfaceControlWithBackground(s,
-                    name, w, h, format, flags);
+                    name, w, h, format, flags, animator.mWin.mAppToken);
         } else if (DEBUG_SURFACE_TRACE) {
             mSurfaceControl = new SurfaceTrace(
                     s, name, w, h, format, flags);
@@ -758,18 +759,25 @@
         }
     }
 
-    private static class SurfaceControlWithBackground extends SurfaceControl {
+    class SurfaceControlWithBackground extends SurfaceControl {
         private SurfaceControl mBackgroundControl;
         private boolean mOpaque = true;
-        private boolean mVisible = false;
+        private boolean mAppForcedInvisible = false;
+        private AppWindowToken mAppToken;
+        public boolean mVisible = false;
+        public int mLayer = -1;
 
         public SurfaceControlWithBackground(SurfaceSession s,
-                       String name, int w, int h, int format, int flags)
+                        String name, int w, int h, int format, int flags,
+                        AppWindowToken token)
                    throws OutOfResourcesException {
             super(s, name, w, h, format, flags);
             mBackgroundControl = new SurfaceControl(s, name, w, h,
                     PixelFormat.OPAQUE, flags | SurfaceControl.FX_SURFACE_DIM);
             mOpaque = (flags & SurfaceControl.OPAQUE) != 0;
+            mAppToken = token;
+
+            mAppToken.addSurfaceViewBackground(this);
         }
 
         @Override
@@ -782,6 +790,10 @@
         public void setLayer(int zorder) {
             super.setLayer(zorder);
             mBackgroundControl.setLayer(zorder - 1);
+            if (mLayer != zorder) {
+                mLayer = zorder;
+                mAppToken.updateSurfaceViewBackgroundVisibilities();
+            }
         }
 
         @Override
@@ -818,7 +830,7 @@
         public void setOpaque(boolean isOpaque) {
             super.setOpaque(isOpaque);
             mOpaque = isOpaque;
-            updateBackgroundVisibility();
+            updateBackgroundVisibility(mAppForcedInvisible);
         }
 
         @Override
@@ -834,23 +846,28 @@
 
         @Override
         public void hide() {
-            mVisible = false;
             super.hide();
-            updateBackgroundVisibility();
+            if (mVisible) {
+                mVisible = false;
+                mAppToken.updateSurfaceViewBackgroundVisibilities();
+            }
         }
 
         @Override
         public void show() {
-            mVisible = true;
             super.show();
-            updateBackgroundVisibility();
+            if (!mVisible) {
+                mVisible = true;
+                mAppToken.updateSurfaceViewBackgroundVisibilities();
+            }
         }
 
         @Override
         public void destroy() {
             super.destroy();
             mBackgroundControl.destroy();
-        }
+            mAppToken.removeSurfaceViewBackground(this);
+         }
 
         @Override
         public void release() {
@@ -870,8 +887,9 @@
             mBackgroundControl.deferTransactionUntil(handle, frame);
         }
 
-        private void updateBackgroundVisibility() {
-            if (mOpaque && mVisible) {
+        void updateBackgroundVisibility(boolean forcedInvisible) {
+            mAppForcedInvisible = forcedInvisible;
+            if (mOpaque && mVisible && !mAppForcedInvisible) {
                 mBackgroundControl.show();
             } else {
                 mBackgroundControl.hide();
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 359063c..e5f9728 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -1074,6 +1074,8 @@
         if (!transitionGoodToGo(appsCount)) {
             return 0;
         }
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");
+
         if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO");
         int transit = mService.mAppTransition.getAppTransition();
         if (mService.mSkipAppTransitionAnimation) {
@@ -1207,6 +1209,9 @@
                 true /*updateInputWindows*/);
         mService.mFocusMayChange = false;
         mService.notifyActivityDrawnForKeyguard();
+
+        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+
         return FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_CONFIG;
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index b528016..c42d461 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -8193,6 +8193,12 @@
 
     @Override
     public SystemUpdatePolicy getSystemUpdatePolicy() {
+        if (UserManager.isDeviceInDemoMode(mContext)) {
+            // Pretending to have an automatic update policy when the device is in retail demo
+            // mode. This will allow the device to download and install an ota without
+            // any user interaction.
+            return SystemUpdatePolicy.createAutomaticInstallPolicy();
+        }
         synchronized (this) {
             SystemUpdatePolicy policy =  mOwners.getSystemUpdatePolicy();
             if (policy != null && !policy.isValid()) {
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index 96c852b..5852626 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -30,6 +30,7 @@
 import android.net.InterfaceConfiguration;
 import android.net.LinkAddress;
 import android.net.NetworkUtils;
+import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.DhcpClientEvent;
 import android.net.metrics.DhcpErrorEvent;
 import android.os.Message;
@@ -163,6 +164,7 @@
     // System services / libraries we use.
     private final Context mContext;
     private final Random mRandom;
+    private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
 
     // Sockets.
     // - We use a packet socket to receive, because servers send us packets bound for IP addresses
@@ -356,14 +358,14 @@
                 } catch (IOException|ErrnoException e) {
                     if (!mStopped) {
                         Log.e(TAG, "Read error", e);
-                        DhcpErrorEvent.logReceiveError(mIfaceName);
+                        logError(DhcpErrorEvent.RECEIVE_ERROR);
                     }
                 } catch (DhcpPacket.ParseException e) {
                     Log.e(TAG, "Can't parse packet: " + e.getMessage());
                     if (PACKET_DBG) {
                         Log.d(TAG, HexDump.dumpHexString(mPacket, 0, length));
                     }
-                    DhcpErrorEvent.logParseError(mIfaceName, e.errorCode);
+                    logError(e.errorCode);
                 }
             }
             if (DBG) Log.d(TAG, "Receive thread stopped");
@@ -493,7 +495,7 @@
         @Override
         public void enter() {
             if (STATE_DBG) Log.d(TAG, "Entering state " + getName());
-            DhcpClientEvent.logStateEvent(mIfaceName, getName());
+            mMetricsLog.log(new DhcpClientEvent(mIfaceName, getName()));
         }
 
         private String messageName(int what) {
@@ -977,4 +979,8 @@
 
     class DhcpRebootingState extends LoggingState {
     }
+
+    private void logError(int errorCode) {
+        mMetricsLog.log(new DhcpErrorEvent(mIfaceName, errorCode));
+    }
 }
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index cece6c8..86e1518 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -31,6 +31,7 @@
 import android.net.RouteInfo;
 import android.net.StaticIpConfiguration;
 import android.net.dhcp.DhcpClient;
+import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.IpManagerEvent;
 import android.os.INetworkManagementService;
 import android.os.Message;
@@ -393,6 +394,7 @@
     private final WakeupMessage mProvisioningTimeoutAlarm;
     private final WakeupMessage mDhcpActionTimeoutAlarm;
     private final LocalLog mLocalLog;
+    private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
 
     private NetworkInterface mNetworkInterface;
 
@@ -634,8 +636,8 @@
 
     private void recordMetric(final int type) {
         if (mStartTimeMillis <= 0) { Log.wtf(mTag, "Start time undefined!"); }
-        IpManagerEvent.logEvent(type, mInterfaceName,
-                SystemClock.elapsedRealtime() - mStartTimeMillis);
+        final long duration = SystemClock.elapsedRealtime() - mStartTimeMillis;
+        mMetricsLog.log(new IpManagerEvent(mInterfaceName, type, duration));
     }
 
     // For now: use WifiStateMachine's historical notion of provisioned.
diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/services/net/java/android/net/ip/IpReachabilityMonitor.java
index 27600a7..afb644f 100644
--- a/services/net/java/android/net/ip/IpReachabilityMonitor.java
+++ b/services/net/java/android/net/ip/IpReachabilityMonitor.java
@@ -24,6 +24,7 @@
 import android.net.LinkProperties.ProvisioningChange;
 import android.net.ProxyInfo;
 import android.net.RouteInfo;
+import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.IpReachabilityEvent;
 import android.net.netlink.NetlinkConstants;
 import android.net.netlink.NetlinkErrorMessage;
@@ -151,6 +152,7 @@
     private final Callback mCallback;
     private final NetlinkSocketObserver mNetlinkSocketObserver;
     private final Thread mObserverThread;
+    private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
     @GuardedBy("mLock")
     private LinkProperties mLinkProperties = new LinkProperties();
     // TODO: consider a map to a private NeighborState class holding more
@@ -359,7 +361,6 @@
         }
 
         if (delta == ProvisioningChange.LOST_PROVISIONING) {
-            IpReachabilityEvent.logProvisioningLost(mInterfaceName);
             final String logMsg = "FAILURE: LOST_PROVISIONING, " + msg;
             Log.w(TAG, logMsg);
             if (mCallback != null) {
@@ -367,8 +368,9 @@
                 // an InetAddress argument.
                 mCallback.notifyLost(ip, logMsg);
             }
+            logEvent(IpReachabilityEvent.PROVISIONING_LOST, 0);
         } else {
-            IpReachabilityEvent.logNudFailed(mInterfaceName);
+            logEvent(IpReachabilityEvent.NUD_FAILED, 0);
         }
     }
 
@@ -393,7 +395,7 @@
                 break;
             }
             final int returnValue = probeNeighbor(mInterfaceIndex, target);
-            IpReachabilityEvent.logProbeEvent(mInterfaceName, returnValue);
+            logEvent(IpReachabilityEvent.PROBE, returnValue);
         }
     }
 
@@ -413,6 +415,11 @@
         return (numUnicastProbes * retransTimeMs) + gracePeriodMs;
     }
 
+    private void logEvent(int probeType, int errorCode) {
+        int eventType = probeType | (errorCode & 0xff );
+        mMetricsLog.log(new IpReachabilityEvent(mInterfaceName, eventType));
+    }
+
     // TODO: simplify the number of objects by making this extend Thread.
     private final class NetlinkSocketObserver implements Runnable {
         private NetlinkSocket mSocket;
diff --git a/services/print/java/com/android/server/print/UserState.java b/services/print/java/com/android/server/print/UserState.java
index 7a3ebf4..05301c1 100644
--- a/services/print/java/com/android/server/print/UserState.java
+++ b/services/print/java/com/android/server/print/UserState.java
@@ -764,9 +764,8 @@
 
     public void updateIfNeededLocked() {
         throwIfDestroyedLocked();
-        if (readConfigurationLocked()) {
-            onConfigurationChangedLocked();
-        }
+        readConfigurationLocked();
+        onConfigurationChangedLocked();
     }
 
     public void destroyLocked() {
@@ -841,14 +840,12 @@
         pw.println();
     }
 
-    private boolean readConfigurationLocked() {
-        boolean somethingChanged = false;
-        somethingChanged |= readInstalledPrintServicesLocked();
-        somethingChanged |= readDisabledPrintServicesLocked();
-        return somethingChanged;
+    private void readConfigurationLocked() {
+        readInstalledPrintServicesLocked();
+        readDisabledPrintServicesLocked();
     }
 
-    private boolean readInstalledPrintServicesLocked() {
+    private void readInstalledPrintServicesLocked() {
         Set<PrintServiceInfo> tempPrintServices = new HashSet<PrintServiceInfo>();
 
         List<ResolveInfo> installedServices = mContext.getPackageManager()
@@ -872,39 +869,8 @@
             tempPrintServices.add(PrintServiceInfo.create(installedService, mContext));
         }
 
-        boolean someServiceChanged = false;
-
-        if (tempPrintServices.size() != mInstalledServices.size()) {
-            someServiceChanged = true;
-        } else {
-            for (PrintServiceInfo newService: tempPrintServices) {
-                final int oldServiceIndex = mInstalledServices.indexOf(newService);
-                if (oldServiceIndex < 0) {
-                    someServiceChanged = true;
-                    break;
-                }
-                // PrintServiceInfo#equals compares only the id not all members,
-                // so we are also comparing the members coming from meta-data.
-                PrintServiceInfo oldService = mInstalledServices.get(oldServiceIndex);
-                if (!TextUtils.equals(oldService.getAddPrintersActivityName(),
-                            newService.getAddPrintersActivityName())
-                        || !TextUtils.equals(oldService.getAdvancedOptionsActivityName(),
-                                newService.getAdvancedOptionsActivityName())
-                        || !TextUtils.equals(oldService.getSettingsActivityName(),
-                                newService.getSettingsActivityName())) {
-                    someServiceChanged = true;
-                    break;
-                }
-            }
-        }
-
-        if (someServiceChanged) {
-            mInstalledServices.clear();
-            mInstalledServices.addAll(tempPrintServices);
-            return true;
-        }
-
-        return false;
+        mInstalledServices.clear();
+        mInstalledServices.addAll(tempPrintServices);
     }
 
     /**
@@ -944,16 +910,14 @@
      *
      * @return true if the state changed.
      */
-    private boolean readDisabledPrintServicesLocked() {
+    private void readDisabledPrintServicesLocked() {
         Set<ComponentName> tempDisabledServiceNameSet = new HashSet<ComponentName>();
         readPrintServicesFromSettingLocked(Settings.Secure.DISABLED_PRINT_SERVICES,
                 tempDisabledServiceNameSet);
         if (!tempDisabledServiceNameSet.equals(mDisabledServices)) {
             mDisabledServices.clear();
             mDisabledServices.addAll(tempDisabledServiceNameSet);
-            return true;
         }
-        return false;
     }
 
     private void readPrintServicesFromSettingLocked(String setting,
diff --git a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
index 51b1e38..d858e82 100644
--- a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
+++ b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
@@ -106,7 +106,7 @@
     private PendingIntent mResetDemoPendingIntent;
     private CameraManager mCameraManager;
     private String[] mCameraIdsWithFlash;
-    private Configuration mPrimaryUserConfiguration;
+    private Configuration mSystemUserConfiguration;
 
     final Object mActivityLock = new Object();
     // Whether the newly created demo user has interacted with the screen yet
@@ -179,8 +179,8 @@
     private void showInactivityCountdownDialog() {
         UserInactivityCountdownDialog dialog = new UserInactivityCountdownDialog(getContext(),
                 WARNING_DIALOG_TIMEOUT, MILLIS_PER_SECOND);
-        dialog.setPositiveButtonClickListener(null);
-        dialog.setNegativeButtonClickListener(new DialogInterface.OnClickListener() {
+        dialog.setNegativeButtonClickListener(null);
+        dialog.setPositiveButtonClickListener(new DialogInterface.OnClickListener() {
             @Override
             public void onClick(DialogInterface dialog, int which) {
                 mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
@@ -366,12 +366,12 @@
         }
     }
 
-    private Configuration getPrimaryUsersConfiguration() {
-        if (mPrimaryUserConfiguration == null) {
+    private Configuration getSystemUsersConfiguration() {
+        if (mSystemUserConfiguration == null) {
             Settings.System.getConfiguration(getContext().getContentResolver(),
-                    mPrimaryUserConfiguration = new Configuration());
+                    mSystemUserConfiguration = new Configuration());
         }
-        return mPrimaryUserConfiguration;
+        return mSystemUserConfiguration;
     }
 
     @Override
@@ -424,10 +424,11 @@
             mWakeLock.acquire();
         }
         mCurrentUserId = userId;
-        mNm.notifyAsUser(TAG, 1, createResetNotification(), UserHandle.of(userId));
+        mAmi.updatePersistentConfigurationForUser(getSystemUsersConfiguration(), userId);
         turnOffAllFlashLights();
         muteVolumeStreams();
-        mAmi.updatePersistentConfigurationForUser(getPrimaryUsersConfiguration(), userId);
+        mNm.notifyAsUser(TAG, 1, createResetNotification(), UserHandle.of(userId));
+
         synchronized (mActivityLock) {
             mUserUntouched = true;
         }
diff --git a/services/retaildemo/java/com/android/server/retaildemo/UserInactivityCountdownDialog.java b/services/retaildemo/java/com/android/server/retaildemo/UserInactivityCountdownDialog.java
index e6548b7..d14f4eb 100644
--- a/services/retaildemo/java/com/android/server/retaildemo/UserInactivityCountdownDialog.java
+++ b/services/retaildemo/java/com/android/server/retaildemo/UserInactivityCountdownDialog.java
@@ -20,8 +20,6 @@
 import android.app.Dialog;
 import android.content.Context;
 import android.os.CountDownTimer;
-import android.view.LayoutInflater;
-import android.view.View;
 import android.view.WindowManager;
 import android.widget.TextView;
 
@@ -30,25 +28,23 @@
 public class UserInactivityCountdownDialog extends AlertDialog {
 
     private OnCountDownExpiredListener mOnCountDownExpiredListener;
-    private View mDialogView;
     private CountDownTimer mCountDownTimer;
     private long mCountDownDuration;
     private long mRefreshInterval;
 
     UserInactivityCountdownDialog(Context context, long duration, long refreshInterval) {
         super(context);
-
         mCountDownDuration = duration;
         mRefreshInterval = refreshInterval;
-        mDialogView = LayoutInflater.from(context).inflate(R.layout.alert_dialog, null);
-        String msg = context.getString(R.string.demo_user_inactivity_timeout_countdown, duration);
-        getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+
+        getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
         WindowManager.LayoutParams attrs = getWindow().getAttributes();
         attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
         getWindow().setAttributes(attrs);
+
         setTitle(R.string.demo_user_inactivity_timeout_title);
-        setView(mDialogView);
-        setMessage(msg);
+        setMessage(getContext().getString(R.string.demo_user_inactivity_timeout_countdown,
+                duration));
     }
 
     public void setOnCountDownExpiredListener(
@@ -58,30 +54,31 @@
 
     public void setPositiveButtonClickListener(OnClickListener onClickListener) {
         setButton(Dialog.BUTTON_POSITIVE,
-                getContext().getString(R.string.demo_user_inactivity_timeout_left_button),
+                getContext().getString(R.string.demo_user_inactivity_timeout_right_button),
                 onClickListener);
     }
 
     public void setNegativeButtonClickListener(OnClickListener onClickListener) {
         setButton(Dialog.BUTTON_NEGATIVE,
-                getContext().getString(R.string.demo_user_inactivity_timeout_right_button),
+                getContext().getString(R.string.demo_user_inactivity_timeout_left_button),
                 onClickListener);
     }
 
     @Override
     public void show() {
         super.show();
-        mDialogView.post(new Runnable() {
+        final TextView messageView = (TextView) findViewById(R.id.message);
+        messageView.post(new Runnable() {
             @Override
             public void run() {
                 mCountDownTimer = new CountDownTimer(mCountDownDuration, mRefreshInterval) {
 
                     @Override
                     public void onTick(long millisUntilFinished) {
-                        String msg = getContext().getResources().getString(
+                        String msg = getContext().getString(
                                 R.string.demo_user_inactivity_timeout_countdown,
                                 millisUntilFinished / 1000);
-                        ((TextView) mDialogView.findViewById(R.id.message)).setText(msg);
+                        messageView.setText(msg);
                     }
 
                     @Override
@@ -96,8 +93,7 @@
     }
 
     @Override
-    public void dismiss() {
-        super.dismiss();
+    public void onStop() {
         if (mCountDownTimer != null) {
             mCountDownTimer.cancel();
         }
diff --git a/services/tests/servicestests/src/android/net/metrics/IpConnectivityLogTest.java b/services/tests/servicestests/src/android/net/metrics/IpConnectivityLogTest.java
new file mode 100644
index 0000000..1433f95
--- /dev/null
+++ b/services/tests/servicestests/src/android/net/metrics/IpConnectivityLogTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.metrics;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.net.ConnectivityMetricsEvent;
+import android.net.IConnectivityMetricsLogger;
+
+import junit.framework.TestCase;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+
+public class IpConnectivityLogTest extends TestCase {
+
+    // use same Parcel object everywhere for pointer equality
+    static final Bundle FAKE_EV = new Bundle();
+
+    @Mock IConnectivityMetricsLogger mService;
+    ArgumentCaptor<ConnectivityMetricsEvent> evCaptor;
+
+    IpConnectivityLog mLog;
+
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        evCaptor = ArgumentCaptor.forClass(ConnectivityMetricsEvent.class);
+        mLog = new IpConnectivityLog(mService);
+    }
+
+    public void testLogEvents() throws Exception {
+        assertTrue(mLog.log(1, FAKE_EV));
+        assertTrue(mLog.log(2, FAKE_EV));
+        assertTrue(mLog.log(3, FAKE_EV));
+
+        List<ConnectivityMetricsEvent> gotEvents = verifyEvents(3);
+        assertEventsEqual(expectedEvent(1), gotEvents.get(0));
+        assertEventsEqual(expectedEvent(2), gotEvents.get(1));
+        assertEventsEqual(expectedEvent(3), gotEvents.get(2));
+    }
+
+    public void testLogEventTriggerThrottling() throws Exception {
+        when(mService.logEvent(any())).thenReturn(1234L);
+
+        assertFalse(mLog.log(1, FAKE_EV));
+    }
+
+    public void testLogEventFails() throws Exception {
+        when(mService.logEvent(any())).thenReturn(-1L); // Error.
+
+        assertFalse(mLog.log(1, FAKE_EV));
+    }
+
+    public void testLogEventWhenThrottling() throws Exception {
+        when(mService.logEvent(any())).thenReturn(Long.MAX_VALUE); // Throttled
+
+        // No events are logged. The service is only called once
+        // After that, throttling state is maintained locally.
+        assertFalse(mLog.log(1, FAKE_EV));
+        assertFalse(mLog.log(2, FAKE_EV));
+
+        List<ConnectivityMetricsEvent> gotEvents = verifyEvents(1);
+        assertEventsEqual(expectedEvent(1), gotEvents.get(0));
+    }
+
+    public void testLogEventRecoverFromThrottling() throws Exception {
+        final long throttleTimeout = System.currentTimeMillis() + 50;
+        when(mService.logEvent(any())).thenReturn(throttleTimeout, 0L);
+
+        assertFalse(mLog.log(1, FAKE_EV));
+        new Thread() {
+            public void run() {
+                busySpinLog();
+            }
+        }.start();
+
+        List<ConnectivityMetricsEvent> gotEvents = verifyEvents(2, 200);
+        assertEventsEqual(expectedEvent(1), gotEvents.get(0));
+        assertEventsEqual(expectedEvent(2), gotEvents.get(1));
+    }
+
+    public void testLogEventRecoverFromThrottlingWithMultipleCallers() throws Exception {
+        final long throttleTimeout = System.currentTimeMillis() + 50;
+        when(mService.logEvent(any())).thenReturn(throttleTimeout, 0L);
+
+        assertFalse(mLog.log(1, FAKE_EV));
+        final int nCallers = 10;
+        for (int i = 0; i < nCallers; i++) {
+            new Thread() {
+                public void run() {
+                    busySpinLog();
+                }
+            }.start();
+        }
+
+        List<ConnectivityMetricsEvent> gotEvents = verifyEvents(1 + nCallers, 200);
+        assertEventsEqual(expectedEvent(1), gotEvents.get(0));
+        for (int i = 0; i < nCallers; i++) {
+            assertEventsEqual(expectedEvent(2), gotEvents.get(1 + i));
+        }
+    }
+
+    void busySpinLog() {
+        final long timeout = 200;
+        final long stop = System.currentTimeMillis() + timeout;
+        try {
+            while (System.currentTimeMillis() < stop) {
+                if (mLog.log(2, FAKE_EV)) {
+                    return;
+                }
+                Thread.sleep(10);
+            }
+        } catch (InterruptedException e) { }
+    }
+
+    List<ConnectivityMetricsEvent> verifyEvents(int n) throws Exception {
+        verify(mService, times(n)).logEvent(evCaptor.capture());
+        return evCaptor.getAllValues();
+    }
+
+    List<ConnectivityMetricsEvent> verifyEvents(int n, int timeoutMs) throws Exception {
+        verify(mService, timeout(timeoutMs).times(n)).logEvent(evCaptor.capture());
+        return evCaptor.getAllValues();
+    }
+
+    static ConnectivityMetricsEvent expectedEvent(int timestamp) {
+        return new ConnectivityMetricsEvent((long)timestamp, 0, 0, FAKE_EV);
+    }
+
+    /** Outer equality for ConnectivityMetricsEvent to avoid overriding equals() and hashCode(). */
+    static void assertEventsEqual(ConnectivityMetricsEvent expected, ConnectivityMetricsEvent got) {
+        assertEquals(expected.timestamp, got.timestamp);
+        assertEquals(expected.componentTag, got.componentTag);
+        assertEquals(expected.eventTag, got.eventTag);
+        assertEquals(expected.data, got.data);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/DnsEventListenerServiceTest.java b/services/tests/servicestests/src/com/android/server/connectivity/DnsEventListenerServiceTest.java
new file mode 100644
index 0000000..033b2c9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/connectivity/DnsEventListenerServiceTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.metrics.DnsEvent;
+import android.net.metrics.IDnsEventListener;
+import android.net.metrics.IpConnectivityLog;
+
+import junit.framework.TestCase;
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.List;
+import java.util.OptionalInt;
+import java.util.stream.IntStream;
+
+public class DnsEventListenerServiceTest extends TestCase {
+
+    // TODO: read from DnsEventListenerService after this constant is read from system property
+    static final int BATCH_SIZE = 100;
+    static final int EVENT_TYPE = IDnsEventListener.EVENT_GETADDRINFO;
+    // TODO: read from IDnsEventListener
+    static final int RETURN_CODE = 1;
+
+    static final byte[] EVENT_TYPES  = new byte[BATCH_SIZE];
+    static final byte[] RETURN_CODES = new byte[BATCH_SIZE];
+    static final int[] LATENCIES     = new int[BATCH_SIZE];
+    static {
+        for (int i = 0; i < BATCH_SIZE; i++) {
+            EVENT_TYPES[i] = EVENT_TYPE;
+            RETURN_CODES[i] = RETURN_CODE;
+            LATENCIES[i] = i;
+        }
+    }
+
+    DnsEventListenerService mDnsService;
+
+    @Mock ConnectivityManager mCm;
+    @Mock IpConnectivityLog mLog;
+    ArgumentCaptor<NetworkCallback> mCallbackCaptor;
+    ArgumentCaptor<DnsEvent> mEvCaptor;
+
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mCallbackCaptor = ArgumentCaptor.forClass(NetworkCallback.class);
+        mEvCaptor = ArgumentCaptor.forClass(DnsEvent.class);
+        mDnsService = new DnsEventListenerService(mCm, mLog);
+
+        verify(mCm, times(1)).registerNetworkCallback(any(), mCallbackCaptor.capture());
+    }
+
+    public void testOneBatch() throws Exception {
+        log(105, LATENCIES);
+        log(106, Arrays.copyOf(LATENCIES, BATCH_SIZE - 1)); // one lookup short of a batch event
+
+        verifyLoggedEvents(new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES));
+
+        log(106, Arrays.copyOfRange(LATENCIES, BATCH_SIZE - 1, BATCH_SIZE));
+
+        mEvCaptor = ArgumentCaptor.forClass(DnsEvent.class); // reset argument captor
+        verifyLoggedEvents(
+            new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
+            new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES));
+    }
+
+    public void testSeveralBatches() throws Exception {
+        log(105, LATENCIES);
+        log(106, LATENCIES);
+        log(105, LATENCIES);
+        log(107, LATENCIES);
+
+        verifyLoggedEvents(
+            new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
+            new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES),
+            new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
+            new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES));
+    }
+
+    public void testBatchAndNetworkLost() throws Exception {
+        byte[] eventTypes = Arrays.copyOf(EVENT_TYPES, 20);
+        byte[] returnCodes = Arrays.copyOf(RETURN_CODES, 20);
+        int[] latencies = Arrays.copyOf(LATENCIES, 20);
+
+        log(105, LATENCIES);
+        log(105, latencies);
+        mCallbackCaptor.getValue().onLost(new Network(105));
+        log(105, LATENCIES);
+
+        verifyLoggedEvents(
+            new DnsEvent(105, eventTypes, returnCodes, latencies),
+            new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
+            new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES));
+    }
+
+    public void testConcurrentBatchesAndDumps() throws Exception {
+        final long stop = System.currentTimeMillis() + 100;
+        final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null"));
+        new Thread() {
+            public void run() {
+                while (System.currentTimeMillis() < stop) {
+                    mDnsService.dump(pw);
+                }
+            }
+        }.start();
+
+        logAsync(105, LATENCIES);
+        logAsync(106, LATENCIES);
+        logAsync(107, LATENCIES);
+
+        verifyLoggedEvents(500,
+            new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
+            new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES),
+            new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES));
+    }
+
+    public void testConcurrentBatchesAndNetworkLoss() throws Exception {
+        logAsync(105, LATENCIES);
+        Thread.sleep(10L);
+        // call onLost() asynchronously to logAsync's onDnsEvent() calls.
+        mCallbackCaptor.getValue().onLost(new Network(105));
+
+        // do not verify unpredictable batch
+        verify(mLog, timeout(500).times(1)).log(any());
+    }
+
+    void log(int netId, int[] latencies) {
+        for (int l : latencies) {
+            mDnsService.onDnsEvent(netId, EVENT_TYPE, RETURN_CODE, l);
+        }
+    }
+
+    void logAsync(int netId, int[] latencies) {
+        new Thread() {
+            public void run() {
+                log(netId, latencies);
+            }
+        }.start();
+    }
+
+    void verifyLoggedEvents(DnsEvent... expected) {
+        verifyLoggedEvents(0, expected);
+    }
+
+    void verifyLoggedEvents(int wait, DnsEvent... expectedEvents) {
+        verify(mLog, timeout(wait).times(expectedEvents.length)).log(mEvCaptor.capture());
+        for (DnsEvent got : mEvCaptor.getAllValues()) {
+            OptionalInt index = IntStream.range(0, expectedEvents.length)
+                    .filter(i -> eventsEqual(expectedEvents[i], got))
+                    .findFirst();
+            // Don't match same expected event more than once.
+            index.ifPresent(i -> expectedEvents[i] = null);
+            assertTrue(index.isPresent());
+        }
+    }
+
+    /** equality function for DnsEvent to avoid overriding equals() and hashCode(). */
+    static boolean eventsEqual(DnsEvent expected, DnsEvent got) {
+        return (expected == got) || ((expected != null) && (got != null)
+                && (expected.netId == got.netId)
+                && Arrays.equals(expected.eventTypes, got.eventTypes)
+                && Arrays.equals(expected.returnCodes, got.returnCodes)
+                && Arrays.equals(expected.latenciesMs, got.latenciesMs));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/MetricsLoggerServiceTest.java b/services/tests/servicestests/src/com/android/server/connectivity/MetricsLoggerServiceTest.java
new file mode 100644
index 0000000..5f84ea1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/connectivity/MetricsLoggerServiceTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import android.content.Context;
+import android.net.ConnectivityMetricsEvent;
+import android.os.Bundle;
+import android.os.RemoteException;
+import static android.net.ConnectivityMetricsEvent.Reference;
+
+import junit.framework.TestCase;
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertArrayEquals;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/*
+ * TODO:
+ *  - allow overriding MetricsLoggerService constants in tests.
+ *  - test intents are correctly sent after the notification threshold.
+ *  - test oldest events are correctly pushed out when internal deque is full.
+ *  - test throttling triggers correctly.
+ */
+public class MetricsLoggerServiceTest extends TestCase {
+
+    static final int COMPONENT_TAG = 1;
+    static final long N_EVENTS = 10L;
+    static final ConnectivityMetricsEvent EVENTS[] = new ConnectivityMetricsEvent[(int)N_EVENTS];
+    static {
+        for (int i = 0; i < N_EVENTS; i++) {
+            EVENTS[i] = new ConnectivityMetricsEvent(i, COMPONENT_TAG, i, new Bundle());
+        }
+    }
+
+    static final ConnectivityMetricsEvent NO_EVENTS[] = new ConnectivityMetricsEvent[0];
+
+    @Mock Context mContext;
+    MetricsLoggerService mService;
+
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mService = new MetricsLoggerService(mContext);
+        mService.onStart();
+    }
+
+    public void testGetNoEvents() throws Exception {
+        Reference r = new Reference(0);
+        assertArrayEquals(NO_EVENTS, mService.mBinder.getEvents(r));
+        assertEquals(0, r.getValue());
+    }
+
+    public void testLogAndGetEvents() throws Exception {
+        mService.mBinder.logEvents(EVENTS);
+
+        Reference r = new Reference(0);
+
+        assertArrayEquals(EVENTS, mService.mBinder.getEvents(r));
+        assertEquals(N_EVENTS, r.getValue());
+
+        assertArrayEquals(NO_EVENTS, mService.mBinder.getEvents(r));
+        assertEquals(N_EVENTS, r.getValue());
+    }
+
+    public void testLogOneByOne() throws Exception {
+        for (ConnectivityMetricsEvent ev : EVENTS) {
+            mService.mBinder.logEvent(ev);
+        }
+
+        Reference r = new Reference(0);
+
+        assertArrayEquals(EVENTS, mService.mBinder.getEvents(r));
+        assertEquals(N_EVENTS, r.getValue());
+
+        assertArrayEquals(NO_EVENTS, mService.mBinder.getEvents(r));
+        assertEquals(N_EVENTS, r.getValue());
+    }
+
+    public void testInterleavedLogAndGet() throws Exception {
+        mService.mBinder.logEvents(Arrays.copyOfRange(EVENTS, 0, 3));
+
+        Reference r = new Reference(0);
+
+        assertArrayEquals(Arrays.copyOfRange(EVENTS, 0, 3), mService.mBinder.getEvents(r));
+        assertEquals(3, r.getValue());
+
+        mService.mBinder.logEvents(Arrays.copyOfRange(EVENTS, 3, 8));
+        mService.mBinder.logEvents(Arrays.copyOfRange(EVENTS, 8, 10));
+
+        assertArrayEquals(Arrays.copyOfRange(EVENTS, 3, 10), mService.mBinder.getEvents(r));
+        assertEquals(N_EVENTS, r.getValue());
+
+        assertArrayEquals(NO_EVENTS, mService.mBinder.getEvents(r));
+        assertEquals(N_EVENTS, r.getValue());
+    }
+
+    public void testMultipleGetAll() throws Exception {
+        mService.mBinder.logEvents(Arrays.copyOf(EVENTS, 3));
+
+        Reference r1 = new Reference(0);
+        assertArrayEquals(Arrays.copyOf(EVENTS, 3), mService.mBinder.getEvents(r1));
+        assertEquals(3, r1.getValue());
+
+        mService.mBinder.logEvents(Arrays.copyOfRange(EVENTS, 3, 10));
+
+        Reference r2 = new Reference(0);
+        assertArrayEquals(EVENTS, mService.mBinder.getEvents(r2));
+        assertEquals(N_EVENTS, r2.getValue());
+    }
+
+    public void testLogAndDumpConcurrently() throws Exception {
+        for (int i = 0; i < 50; i++) {
+            mContext = null;
+            mService = null;
+            setUp();
+            logAndDumpConcurrently();
+        }
+    }
+
+    public void logAndDumpConcurrently() throws Exception {
+        final CountDownLatch latch = new CountDownLatch((int)N_EVENTS);
+        final FileDescriptor fd = new FileOutputStream("/dev/null").getFD();
+
+        for (ConnectivityMetricsEvent ev : EVENTS) {
+            new Thread() {
+                public void run() {
+                    mService.mBinder.logEvent(ev);
+                    latch.countDown();
+                }
+            }.start();
+        }
+
+        new Thread() {
+            public void run() {
+                while (latch.getCount() > 0) {
+                    mService.mBinder.dump(fd, new String[]{"--all"});
+                }
+            }
+        }.start();
+
+        latch.await(100, TimeUnit.MILLISECONDS);
+
+        Reference r = new Reference(0);
+        ConnectivityMetricsEvent[] got = mService.mBinder.getEvents(r);
+        Arrays.sort(got, new EventComparator());
+        assertArrayEquals(EVENTS, got);
+        assertEquals(N_EVENTS, r.getValue());
+    }
+
+    static class EventComparator implements Comparator<ConnectivityMetricsEvent> {
+        public int compare(ConnectivityMetricsEvent ev1, ConnectivityMetricsEvent ev2) {
+            return Long.compare(ev1.timestamp, ev2.timestamp);
+        }
+        public boolean equal(Object o) {
+            return o instanceof EventComparator;
+        }
+    };
+}