Merge "Carrier confirmation code."
diff --git a/Android.mk b/Android.mk
index a7cb362..c4f222e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -398,6 +398,7 @@
 	core/java/com/android/internal/backup/IObbBackupService.aidl \
 	core/java/com/android/internal/car/ICarServiceHelper.aidl \
 	core/java/com/android/internal/inputmethod/IInputContentUriToken.aidl \
+	core/java/com/android/internal/net/INetworkWatchlistManager.aidl \
 	core/java/com/android/internal/policy/IKeyguardDrawnCallback.aidl \
 	core/java/com/android/internal/policy/IKeyguardDismissCallback.aidl \
 	core/java/com/android/internal/policy/IKeyguardExitCallback.aidl \
diff --git a/api/current.txt b/api/current.txt
index 97b065e..823cc3f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -31960,6 +31960,7 @@
     field public static final java.lang.String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
     field public static final java.lang.String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";
     field public static final java.lang.String DISALLOW_CONFIG_DATE_TIME = "no_config_date_time";
+    field public static final java.lang.String DISALLOW_CONFIG_LOCALE = "no_config_locale";
     field public static final java.lang.String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks";
     field public static final java.lang.String DISALLOW_CONFIG_TETHERING = "no_config_tethering";
     field public static final java.lang.String DISALLOW_CONFIG_VPN = "no_config_vpn";
diff --git a/api/system-current.txt b/api/system-current.txt
index 61aaf18..76a12c5 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -34812,6 +34812,7 @@
     field public static final java.lang.String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
     field public static final java.lang.String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";
     field public static final java.lang.String DISALLOW_CONFIG_DATE_TIME = "no_config_date_time";
+    field public static final java.lang.String DISALLOW_CONFIG_LOCALE = "no_config_locale";
     field public static final java.lang.String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks";
     field public static final java.lang.String DISALLOW_CONFIG_TETHERING = "no_config_tethering";
     field public static final java.lang.String DISALLOW_CONFIG_VPN = "no_config_vpn";
diff --git a/api/test-current.txt b/api/test-current.txt
index 2a2967e..0cb6055 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -32229,6 +32229,7 @@
     field public static final java.lang.String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
     field public static final java.lang.String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";
     field public static final java.lang.String DISALLOW_CONFIG_DATE_TIME = "no_config_date_time";
+    field public static final java.lang.String DISALLOW_CONFIG_LOCALE = "no_config_locale";
     field public static final java.lang.String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks";
     field public static final java.lang.String DISALLOW_CONFIG_TETHERING = "no_config_tethering";
     field public static final java.lang.String DISALLOW_CONFIG_VPN = "no_config_vpn";
diff --git a/config/compiled-classes-phone b/config/compiled-classes-phone
index bc344d7..fb201ef 100644
--- a/config/compiled-classes-phone
+++ b/config/compiled-classes-phone
@@ -5205,6 +5205,8 @@
 com.android.internal.app.AlertController$ButtonHandler
 com.android.internal.app.AlertController$RecycleListView
 com.android.internal.app.AssistUtils
+com.android.internal.app.ColorDisplayController
+com.android.internal.app.ColorDisplayController$Callback
 com.android.internal.app.IAppOpsCallback
 com.android.internal.app.IAppOpsCallback$Stub
 com.android.internal.app.IAppOpsCallback$Stub$Proxy
@@ -5230,8 +5232,6 @@
 com.android.internal.app.IVoiceInteractionSessionShowCallback$Stub
 com.android.internal.app.IVoiceInteractor
 com.android.internal.app.IVoiceInteractor$Stub
-com.android.internal.app.NightDisplayController
-com.android.internal.app.NightDisplayController$Callback
 com.android.internal.app.ProcessMap
 com.android.internal.app.ResolverActivity
 com.android.internal.app.ToolbarActionBar
diff --git a/config/preloaded-classes b/config/preloaded-classes
index a9e90de..784c3f8 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -2768,6 +2768,7 @@
 com.android.ims.ImsException
 com.android.internal.R$styleable
 com.android.internal.app.AlertController$AlertParams
+com.android.internal.app.ColorDisplayController
 com.android.internal.app.IAppOpsCallback
 com.android.internal.app.IAppOpsCallback$Stub
 com.android.internal.app.IAppOpsService
@@ -2780,7 +2781,6 @@
 com.android.internal.app.IVoiceInteractionManagerService$Stub
 com.android.internal.app.IVoiceInteractor
 com.android.internal.app.IVoiceInteractor$Stub
-com.android.internal.app.NightDisplayController
 com.android.internal.appwidget.IAppWidgetService
 com.android.internal.appwidget.IAppWidgetService$Stub
 com.android.internal.appwidget.IAppWidgetService$Stub$Proxy
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 6989db6..4efc2c7 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -81,6 +81,7 @@
 import android.net.IpSecManager;
 import android.net.NetworkPolicyManager;
 import android.net.NetworkScoreManager;
+import android.net.NetworkWatchlistManager;
 import android.net.lowpan.ILowpanManager;
 import android.net.lowpan.LowpanManager;
 import android.net.nsd.INsdManager;
@@ -150,6 +151,7 @@
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.app.ISoundTriggerService;
 import com.android.internal.appwidget.IAppWidgetService;
+import com.android.internal.net.INetworkWatchlistManager;
 import com.android.internal.os.IDropBoxManagerService;
 import com.android.internal.policy.PhoneLayoutInflater;
 
@@ -862,6 +864,17 @@
                 return new ShortcutManager(ctx, IShortcutService.Stub.asInterface(b));
             }});
 
+        registerService(Context.NETWORK_WATCHLIST_SERVICE, NetworkWatchlistManager.class,
+                new CachedServiceFetcher<NetworkWatchlistManager>() {
+                    @Override
+                    public NetworkWatchlistManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        IBinder b =
+                                ServiceManager.getServiceOrThrow(Context.NETWORK_WATCHLIST_SERVICE);
+                        return new NetworkWatchlistManager(ctx,
+                                INetworkWatchlistManager.Stub.asInterface(b));
+                    }});
+
         registerService(Context.SYSTEM_HEALTH_SERVICE, SystemHealthManager.class,
                 new CachedServiceFetcher<SystemHealthManager>() {
             @Override
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index de27b4f..2c1fad1 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -500,15 +500,12 @@
      * @hide
      */
     public boolean supportSplitScreenWindowingMode() {
-        return supportSplitScreenWindowingMode(mWindowingMode, mActivityType);
+        return supportSplitScreenWindowingMode(mActivityType);
     }
 
     /** @hide */
-    public static boolean supportSplitScreenWindowingMode(int windowingMode, int activityType) {
-        if (activityType == ACTIVITY_TYPE_ASSISTANT) {
-            return false;
-        }
-        return windowingMode != WINDOWING_MODE_FREEFORM && windowingMode != WINDOWING_MODE_PINNED;
+    public static boolean supportSplitScreenWindowingMode(int activityType) {
+        return activityType != ACTIVITY_TYPE_ASSISTANT;
     }
 
     /** @hide */
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index dbaace2..29e7439 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -118,7 +118,15 @@
             AppIdleStateChangeListener listener);
 
     public static abstract class AppIdleStateChangeListener {
-        public abstract void onAppIdleStateChanged(String packageName, int userId, boolean idle);
+
+        /** Callback to inform listeners that the idle state has changed to a new bucket. */
+        public abstract void onAppIdleStateChanged(String packageName, int userId, boolean idle,
+                                                   int bucket);
+
+        /**
+         * Callback to inform listeners that the parole state has changed. This means apps are
+         * allowed to do work even if they're idle or in a low bucket.
+         */
         public abstract void onParoleStateChanged(boolean isParoleOn);
     }
 
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index c165fb3..01ad3ad 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3413,6 +3413,8 @@
     public static final String NETWORK_STATS_SERVICE = "netstats";
     /** {@hide} */
     public static final String NETWORK_POLICY_SERVICE = "netpolicy";
+    /** {@hide} */
+    public static final String NETWORK_WATCHLIST_SERVICE = "network_watchlist";
 
     /**
      * Use with {@link #getSystemService} to retrieve a {@link
diff --git a/core/java/android/net/IIpConnectivityMetrics.aidl b/core/java/android/net/IIpConnectivityMetrics.aidl
index 6f07b31..aeaf09d 100644
--- a/core/java/android/net/IIpConnectivityMetrics.aidl
+++ b/core/java/android/net/IIpConnectivityMetrics.aidl
@@ -30,11 +30,11 @@
     int logEvent(in ConnectivityMetricsEvent event);
 
     /**
-     * At most one callback can be registered (by DevicePolicyManager).
+     * Callback can be registered by DevicePolicyManager or NetworkWatchlistService only.
      * @return status {@code true} if registering/unregistering of the callback was successful,
      *         {@code false} otherwise (might happen if IIpConnectivityMetrics is not available,
      *         if it happens make sure you call it when the service is up in the caller)
      */
-    boolean registerNetdEventCallback(in INetdEventCallback callback);
-    boolean unregisterNetdEventCallback();
+    boolean addNetdEventCallback(in int callerType, in INetdEventCallback callback);
+    boolean removeNetdEventCallback(in int callerType);
 }
diff --git a/core/java/android/net/INetdEventCallback.aidl b/core/java/android/net/INetdEventCallback.aidl
index 49436be..1fd9423 100644
--- a/core/java/android/net/INetdEventCallback.aidl
+++ b/core/java/android/net/INetdEventCallback.aidl
@@ -19,6 +19,10 @@
 /** {@hide} */
 oneway interface INetdEventCallback {
 
+    // Possible addNetdEventCallback callers.
+    const int CALLBACK_CALLER_DEVICE_POLICY = 0;
+    const int CALLBACK_CALLER_NETWORK_WATCHLIST = 1;
+
     /**
      * Reports a single DNS lookup function call.
      * This method must not block or perform long-running operations.
diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java
index e76d17d..f6a69ba 100644
--- a/core/java/android/net/MacAddress.java
+++ b/core/java/android/net/MacAddress.java
@@ -16,29 +16,128 @@
 
 package android.net;
 
-import com.android.internal.annotations.VisibleForTesting;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.BitUtils;
 
 import java.util.Arrays;
+import java.util.Random;
+import java.util.StringJoiner;
 
 /**
+ * Represents a mac address.
+ *
  * @hide
  */
-public final class MacAddress {
-
-    // TODO: add isLocallyAssigned().
-    // TODO: add getRandomAddress() factory method.
+public final class MacAddress implements Parcelable {
 
     private static final int ETHER_ADDR_LEN = 6;
-    private static final byte FF = (byte) 0xff;
-    @VisibleForTesting
-    static final byte[] ETHER_ADDR_BROADCAST = { FF, FF, FF, FF, FF, FF };
+    private static final byte[] ETHER_ADDR_BROADCAST = addr(0xff, 0xff, 0xff, 0xff, 0xff, 0xff);
 
+    /** The broadcast mac address.  */
+    public static final MacAddress BROADCAST_ADDRESS = new MacAddress(ETHER_ADDR_BROADCAST);
+
+    /** The zero mac address. */
+    public static final MacAddress ALL_ZEROS_ADDRESS = new MacAddress(0);
+
+    /** Represents categories of mac addresses. */
     public enum MacAddressType {
         UNICAST,
         MULTICAST,
         BROADCAST;
     }
 
+    private static final long VALID_LONG_MASK = BROADCAST_ADDRESS.mAddr;
+    private static final long LOCALLY_ASSIGNED_MASK = new MacAddress("2:0:0:0:0:0").mAddr;
+    private static final long MULTICAST_MASK = new MacAddress("1:0:0:0:0:0").mAddr;
+    private static final long OUI_MASK = new MacAddress("ff:ff:ff:0:0:0").mAddr;
+    private static final long NIC_MASK = new MacAddress("0:0:0:ff:ff:ff").mAddr;
+    private static final MacAddress BASE_ANDROID_MAC = new MacAddress("da:a1:19:0:0:0");
+
+    // Internal representation of the mac address as a single 8 byte long.
+    // The encoding scheme sets the two most significant bytes to 0. The 6 bytes of the
+    // mac address are encoded in the 6 least significant bytes of the long, where the first
+    // byte of the array is mapped to the 3rd highest logical byte of the long, the second
+    // byte of the array is mapped to the 4th highest logical byte of the long, and so on.
+    private final long mAddr;
+
+    private MacAddress(long addr) {
+        mAddr = addr;
+    }
+
+    /** Creates a MacAddress for the given byte representation. */
+    public MacAddress(byte[] addr) {
+        this(longAddrFromByteAddr(addr));
+    }
+
+    /** Creates a MacAddress for the given string representation. */
+    public MacAddress(String addr) {
+        this(longAddrFromByteAddr(byteAddrFromStringAddr(addr)));
+    }
+
+    /** Returns the MacAddressType of this MacAddress. */
+    public MacAddressType addressType() {
+        if (equals(BROADCAST_ADDRESS)) {
+            return MacAddressType.BROADCAST;
+        }
+        if (isMulticastAddress()) {
+            return MacAddressType.MULTICAST;
+        }
+        return MacAddressType.UNICAST;
+    }
+
+    /** Returns true if this MacAddress corresponds to a multicast address. */
+    public boolean isMulticastAddress() {
+        return (mAddr & MULTICAST_MASK) != 0;
+    }
+
+    /** Returns true if this MacAddress corresponds to a locally assigned address. */
+    public boolean isLocallyAssigned() {
+        return (mAddr & LOCALLY_ASSIGNED_MASK) != 0;
+    }
+
+    /** Returns a byte array representation of this MacAddress. */
+    public byte[] toByteArray() {
+        return byteAddrFromLongAddr(mAddr);
+    }
+
+    @Override
+    public String toString() {
+        return stringAddrFromByteAddr(byteAddrFromLongAddr(mAddr));
+    }
+
+    @Override
+    public int hashCode() {
+        return (int) ((mAddr >> 32) ^ mAddr);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return (o instanceof MacAddress) && ((MacAddress) o).mAddr == mAddr;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeLong(mAddr);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<MacAddress> CREATOR =
+            new Parcelable.Creator<MacAddress>() {
+                public MacAddress createFromParcel(Parcel in) {
+                    return new MacAddress(in.readLong());
+                }
+
+                public MacAddress[] newArray(int size) {
+                    return new MacAddress[size];
+                }
+            };
+
     /** Return true if the given byte array is not null and has the length of a mac address. */
     public static boolean isMacAddress(byte[] addr) {
         return addr != null && addr.length == ETHER_ADDR_LEN;
@@ -46,17 +145,130 @@
 
     /**
      * Return the MacAddressType of the mac address represented by the given byte array,
-     * or null if the given byte array does not represent an mac address. */
+     * or null if the given byte array does not represent an mac address.
+     */
     public static MacAddressType macAddressType(byte[] addr) {
         if (!isMacAddress(addr)) {
             return null;
         }
-        if (Arrays.equals(addr, ETHER_ADDR_BROADCAST)) {
-            return MacAddressType.BROADCAST;
+        return new MacAddress(addr).addressType();
+    }
+
+    /** DOCME */
+    public static byte[] byteAddrFromStringAddr(String addr) {
+        if (addr == null) {
+            throw new IllegalArgumentException("cannot convert the null String");
         }
-        if ((addr[0] & 0x01) == 1) {
-            return MacAddressType.MULTICAST;
+        String[] parts = addr.split(":");
+        if (parts.length != ETHER_ADDR_LEN) {
+            throw new IllegalArgumentException(addr + " was not a valid MAC address");
         }
-        return MacAddressType.UNICAST;
+        byte[] bytes = new byte[ETHER_ADDR_LEN];
+        for (int i = 0; i < ETHER_ADDR_LEN; i++) {
+            int x = Integer.valueOf(parts[i], 16);
+            if (x < 0 || 0xff < x) {
+                throw new IllegalArgumentException(addr + "was not a valid MAC address");
+            }
+            bytes[i] = (byte) x;
+        }
+        return bytes;
+    }
+
+    /** DOCME */
+    public static String stringAddrFromByteAddr(byte[] addr) {
+        if (!isMacAddress(addr)) {
+            return null;
+        }
+        StringJoiner j = new StringJoiner(":");
+        for (byte b : addr) {
+            j.add(Integer.toHexString(BitUtils.uint8(b)));
+        }
+        return j.toString();
+    }
+
+    /** @hide */
+    public static byte[] byteAddrFromLongAddr(long addr) {
+        byte[] bytes = new byte[ETHER_ADDR_LEN];
+        int index = ETHER_ADDR_LEN;
+        while (index-- > 0) {
+            bytes[index] = (byte) addr;
+            addr = addr >> 8;
+        }
+        return bytes;
+    }
+
+    /** @hide */
+    public static long longAddrFromByteAddr(byte[] addr) {
+        if (!isMacAddress(addr)) {
+            throw new IllegalArgumentException(
+                    Arrays.toString(addr) + " was not a valid MAC address");
+        }
+        long longAddr = 0;
+        for (byte b : addr) {
+            longAddr = (longAddr << 8) + BitUtils.uint8(b);
+        }
+        return longAddr;
+    }
+
+    /** @hide */
+    public static long longAddrFromStringAddr(String addr) {
+        if (addr == null) {
+            throw new IllegalArgumentException("cannot convert the null String");
+        }
+        String[] parts = addr.split(":");
+        if (parts.length != ETHER_ADDR_LEN) {
+            throw new IllegalArgumentException(addr + " was not a valid MAC address");
+        }
+        long longAddr = 0;
+        int index = ETHER_ADDR_LEN;
+        while (index-- > 0) {
+            int x = Integer.valueOf(parts[index], 16);
+            if (x < 0 || 0xff < x) {
+                throw new IllegalArgumentException(addr + "was not a valid MAC address");
+            }
+            longAddr = x + (longAddr << 8);
+        }
+        return longAddr;
+    }
+
+    /** @hide */
+    public static String stringAddrFromLongAddr(long addr) {
+        addr = Long.reverseBytes(addr) >> 16;
+        StringJoiner j = new StringJoiner(":");
+        for (int i = 0; i < ETHER_ADDR_LEN; i++) {
+            j.add(Integer.toHexString((byte) addr));
+            addr = addr >> 8;
+        }
+        return j.toString();
+    }
+
+    /**
+     * Returns a randomely generated mac address with the Android OUI value "DA-A1-19".
+     * The locally assigned bit is always set to 1.
+     */
+    public static MacAddress getRandomAddress() {
+        return getRandomAddress(BASE_ANDROID_MAC, new Random());
+    }
+
+    /**
+     * Returns a randomely generated mac address using the given Random object and the same
+     * OUI values as the given MacAddress. The locally assigned bit is always set to 1.
+     */
+    public static MacAddress getRandomAddress(MacAddress base, Random r) {
+        long longAddr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong()) | LOCALLY_ASSIGNED_MASK;
+        return new MacAddress(longAddr);
+    }
+
+    // Convenience function for working around the lack of byte literals.
+    private static byte[] addr(int... in) {
+        if (in.length != ETHER_ADDR_LEN) {
+            throw new IllegalArgumentException(Arrays.toString(in)
+                    + " was not an array with length equal to " + ETHER_ADDR_LEN);
+        }
+        byte[] out = new byte[ETHER_ADDR_LEN];
+        for (int i = 0; i < ETHER_ADDR_LEN; i++) {
+            out[i] = (byte) in[i];
+        }
+        return out;
     }
 }
diff --git a/core/java/android/net/NetworkWatchlistManager.java b/core/java/android/net/NetworkWatchlistManager.java
new file mode 100644
index 0000000..42e43c8
--- /dev/null
+++ b/core/java/android/net/NetworkWatchlistManager.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.internal.net.INetworkWatchlistManager;
+import com.android.internal.util.Preconditions;
+
+/**
+ * Class that manage network watchlist in system.
+ * @hide
+ */
+@SystemService(Context.NETWORK_WATCHLIST_SERVICE)
+public class NetworkWatchlistManager {
+
+    private static final String TAG = "NetworkWatchlistManager";
+    private static final String SHARED_MEMORY_TAG = "NETWORK_WATCHLIST_SHARED_MEMORY";
+
+    private final Context mContext;
+    private final INetworkWatchlistManager mNetworkWatchlistManager;
+
+    /**
+     * @hide
+     */
+    public NetworkWatchlistManager(Context context, INetworkWatchlistManager manager) {
+        mContext = context;
+        mNetworkWatchlistManager = manager;
+    }
+
+    /**
+     * @hide
+     */
+    public NetworkWatchlistManager(Context context) {
+        mContext = Preconditions.checkNotNull(context, "missing context");
+        mNetworkWatchlistManager = (INetworkWatchlistManager)
+                INetworkWatchlistManager.Stub.asInterface(
+                        ServiceManager.getService(Context.NETWORK_WATCHLIST_SERVICE));
+    }
+
+    /**
+     * Report network watchlist records if necessary.
+     *
+     * Watchlist report process will run summarize records into a single report, then the
+     * report will be processed by differential privacy framework and store it on disk.
+     *
+     * @hide
+     */
+    public void reportWatchlistIfNecessary() {
+        try {
+            mNetworkWatchlistManager.reportWatchlistIfNecessary();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Cannot report records", e);
+            e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index 0b1569c..4817813 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -63,7 +63,12 @@
  * This implementation does check the server's certificate hostname, but only
  * for createSocket variants that specify a hostname.  When using methods that
  * use {@link InetAddress} or which return an unconnected socket, you MUST
- * verify the server's identity yourself to ensure a secure connection.</p>
+ * verify the server's identity yourself to ensure a secure connection.
+ *
+ * Refer to
+ * <a href="https://developer.android.com/training/articles/security-gms-provider.html">
+ * Updating Your Security Provider to Protect Against SSL Exploits</a>
+ * for further information.</p>
  *
  * <p>One way to verify the server's identity is to use
  * {@link HttpsURLConnection#getDefaultHostnameVerifier()} to get a
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 01b6535..dd4825e 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -526,7 +526,11 @@
             ServiceType.SCREEN_BRIGHTNESS,
             ServiceType.SOUND,
             ServiceType.BATTERY_STATS,
-            ServiceType.DATA_SAVER})
+            ServiceType.DATA_SAVER,
+            ServiceType.FORCE_ALL_APPS_STANDBY_JOBS,
+            ServiceType.FORCE_ALL_APPS_STANDBY_ALARMS,
+            ServiceType.OPTIONAL_SENSORS,
+    })
     public @interface ServiceType {
         int NULL = 0;
         int GPS = 1;
@@ -539,6 +543,21 @@
         int SOUND = 8;
         int BATTERY_STATS = 9;
         int DATA_SAVER = 10;
+
+        /**
+         * Whether the job scheduler should force app standby on all apps on battery saver or not.
+         */
+        int FORCE_ALL_APPS_STANDBY_JOBS = 11;
+
+        /**
+         * Whether the alarm manager should force app standby on all apps on battery saver or not.
+         */
+        int FORCE_ALL_APPS_STANDBY_ALARMS = 12;
+
+        /**
+         * Whether to disable non-essential sensors. (e.g. edge sensors.)
+         */
+        int OPTIONAL_SENSORS = 13;
     }
 
     final Context mContext;
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index a01b8ed..77ac2651 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -18,6 +18,8 @@
 
 import android.view.Display;
 
+import java.util.function.Consumer;
+
 /**
  * Power manager local system service interface.
  *
@@ -125,6 +127,23 @@
 
     public abstract void registerLowPowerModeObserver(LowPowerModeListener listener);
 
+    /**
+     * Same as {@link #registerLowPowerModeObserver} but can take a lambda.
+     */
+    public void registerLowPowerModeObserver(int serviceType, Consumer<PowerSaveState> listener) {
+        registerLowPowerModeObserver(new LowPowerModeListener() {
+            @Override
+            public int getServiceType() {
+                return serviceType;
+            }
+
+            @Override
+            public void onLowPowerModeChanged(PowerSaveState state) {
+                listener.accept(state);
+            }
+        });
+    }
+
     public interface LowPowerModeListener {
         int getServiceType();
         void onLowPowerModeChanged(PowerSaveState state);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 28836e4..de52736 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -140,6 +140,18 @@
     public static final String DISALLOW_CONFIG_WIFI = "no_config_wifi";
 
     /**
+     * Specifies if a user is disallowed from changing the device
+     * language. The default value is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_CONFIG_LOCALE = "no_config_locale";
+
+    /**
      * Specifies if a user is disallowed from installing applications.
      * The default value is <code>false</code>.
      *
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1716305..1bef2b3 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8522,6 +8522,13 @@
        public static final String NETWORK_METERED_MULTIPATH_PREFERENCE =
                "network_metered_multipath_preference";
 
+        /**
+         * Network watchlist last report time.
+         * @hide
+         */
+        public static final String NETWORK_WATCHLIST_LAST_REPORT_TIME =
+                "network_watchlist_last_report_time";
+
        /**
         * The thresholds of the wifi throughput badging (SD, HD etc.) as a comma-delimited list of
         * colon-delimited key-value pairs. The key is the badging enum value defined in
diff --git a/core/java/android/util/apk/ApkVerityBuilder.java b/core/java/android/util/apk/ApkVerityBuilder.java
index b07e94a..7412ef4 100644
--- a/core/java/android/util/apk/ApkVerityBuilder.java
+++ b/core/java/android/util/apk/ApkVerityBuilder.java
@@ -20,6 +20,7 @@
 import java.io.RandomAccessFile;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
+import java.security.DigestException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
@@ -66,7 +67,7 @@
      */
     static ApkVerityResult generateApkVerity(RandomAccessFile apk,
             SignatureInfo signatureInfo, ByteBufferFactory bufferFactory)
-            throws IOException, SecurityException, NoSuchAlgorithmException {
+            throws IOException, SecurityException, NoSuchAlgorithmException, DigestException {
         assertSigningBlockAlignedAndHasFullPages(signatureInfo);
 
         long signingBlockSize =
@@ -112,6 +113,7 @@
         private final ByteBuffer mOutput;
 
         private final MessageDigest mMd;
+        private final byte[] mDigestBuffer = new byte[DIGEST_SIZE_BYTES];
         private final byte[] mSalt;
 
         private BufferedDigester(byte[] salt, ByteBuffer output) throws NoSuchAlgorithmException {
@@ -129,21 +131,22 @@
          * consumption will continuous from there.
          */
         @Override
-        public void consume(ByteBuffer buffer) {
+        public void consume(ByteBuffer buffer) throws DigestException {
             int offset = buffer.position();
             int remaining = buffer.remaining();
             while (remaining > 0) {
                 int allowance = (int) Math.min(remaining, BUFFER_SIZE - mBytesDigestedSinceReset);
-                mMd.update(slice(buffer, offset, offset + allowance));
+                // Optimization: set the buffer limit to avoid allocating a new ByteBuffer object.
+                buffer.limit(buffer.position() + allowance);
+                mMd.update(buffer);
                 offset += allowance;
                 remaining -= allowance;
                 mBytesDigestedSinceReset += allowance;
 
                 if (mBytesDigestedSinceReset == BUFFER_SIZE) {
-                    byte[] digest = mMd.digest();
-                    mOutput.put(digest);
-
-                    mMd.reset();
+                    mMd.digest(mDigestBuffer, 0, mDigestBuffer.length);
+                    mOutput.put(mDigestBuffer);
+                    // After digest, MessageDigest resets automatically, so no need to reset again.
                     mMd.update(mSalt);
                     mBytesDigestedSinceReset = 0;
                 }
@@ -152,12 +155,12 @@
 
         /** Finish the current digestion if any. */
         @Override
-        public void finish() {
+        public void finish() throws DigestException {
             if (mBytesDigestedSinceReset == 0) {
                 return;
             }
-            byte[] digest = mMd.digest();
-            mOutput.put(digest);
+            mMd.digest(mDigestBuffer, 0, mDigestBuffer.length);
+            mOutput.put(mDigestBuffer);
         }
 
         private void fillUpLastOutputChunk() {
@@ -174,7 +177,7 @@
      * digest the remaining.
      */
     private static void consumeByChunk(DataDigester digester, DataSource source, int chunkSize)
-            throws IOException {
+            throws IOException, DigestException {
         long inputRemaining = source.size();
         long inputOffset = 0;
         while (inputRemaining > 0) {
@@ -191,7 +194,7 @@
 
     private static void generateApkVerityDigestAtLeafLevel(RandomAccessFile apk,
             SignatureInfo signatureInfo, byte[] salt, ByteBuffer output)
-            throws IOException, NoSuchAlgorithmException {
+            throws IOException, NoSuchAlgorithmException, DigestException {
         BufferedDigester digester = new BufferedDigester(salt, output);
 
         // 1. Digest from the beginning of the file, until APK Signing Block is reached.
@@ -230,7 +233,7 @@
 
     private static byte[] generateApkVerityTree(RandomAccessFile apk, SignatureInfo signatureInfo,
             byte[] salt, int[] levelOffset, ByteBuffer output)
-            throws IOException, NoSuchAlgorithmException {
+            throws IOException, NoSuchAlgorithmException, DigestException {
         // 1. Digest the apk to generate the leaf level hashes.
         generateApkVerityDigestAtLeafLevel(apk, signatureInfo, salt, slice(output,
                     levelOffset[levelOffset.length - 2], levelOffset[levelOffset.length - 1]));
diff --git a/core/java/android/util/apk/ByteBufferDataSource.java b/core/java/android/util/apk/ByteBufferDataSource.java
index c2b9eff..3976568 100644
--- a/core/java/android/util/apk/ByteBufferDataSource.java
+++ b/core/java/android/util/apk/ByteBufferDataSource.java
@@ -18,6 +18,7 @@
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.security.DigestException;
 
 /**
  * {@link DataSource} which provides data from a {@link ByteBuffer}.
@@ -42,7 +43,7 @@
 
     @Override
     public void feedIntoDataDigester(DataDigester md, long offset, int size)
-            throws IOException {
+            throws IOException, DigestException {
         // There's no way to tell MessageDigest to read data from ByteBuffer from a position
         // other than the buffer's current position. We thus need to change the buffer's
         // position to match the requested offset.
diff --git a/core/java/android/util/apk/DataDigester.java b/core/java/android/util/apk/DataDigester.java
index 74dce7e..278be80 100644
--- a/core/java/android/util/apk/DataDigester.java
+++ b/core/java/android/util/apk/DataDigester.java
@@ -17,11 +17,12 @@
 package android.util.apk;
 
 import java.nio.ByteBuffer;
+import java.security.DigestException;
 
 interface DataDigester {
     /** Consumes the {@link ByteBuffer}. */
-    void consume(ByteBuffer buffer);
+    void consume(ByteBuffer buffer) throws DigestException;
 
     /** Finishes the digestion. Must be called after the last {@link #consume(ByteBuffer)}. */
-    void finish();
+    void finish() throws DigestException;
 }
diff --git a/core/java/android/util/apk/DataSource.java b/core/java/android/util/apk/DataSource.java
index 2b39f49..82f3800 100644
--- a/core/java/android/util/apk/DataSource.java
+++ b/core/java/android/util/apk/DataSource.java
@@ -17,6 +17,7 @@
 package android.util.apk;
 
 import java.io.IOException;
+import java.security.DigestException;
 
 /** Source of data to be digested. */
 interface DataSource {
@@ -32,5 +33,6 @@
      * @param offset offset of the region inside this data source.
      * @param size size (in bytes) of the region.
      */
-    void feedIntoDataDigester(DataDigester md, long offset, int size) throws IOException;
+    void feedIntoDataDigester(DataDigester md, long offset, int size)
+            throws IOException, DigestException;
 }
diff --git a/core/java/android/util/apk/MemoryMappedFileDataSource.java b/core/java/android/util/apk/MemoryMappedFileDataSource.java
index 04a1c6b..8d2b1e32 100644
--- a/core/java/android/util/apk/MemoryMappedFileDataSource.java
+++ b/core/java/android/util/apk/MemoryMappedFileDataSource.java
@@ -24,6 +24,7 @@
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.DirectByteBuffer;
+import java.security.DigestException;
 
 /**
  * {@link DataSource} which provides data from a file descriptor by memory-mapping the sections
@@ -55,7 +56,7 @@
 
     @Override
     public void feedIntoDataDigester(DataDigester md, long offset, int size)
-            throws IOException {
+            throws IOException, DigestException {
         // IMPLEMENTATION NOTE: After a lot of experimentation, the implementation of this
         // method was settled on a straightforward mmap with prefaulting.
         //
diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java
index 7e1846a..cd1b190 100644
--- a/core/java/android/view/WindowManagerInternal.java
+++ b/core/java/android/view/WindowManagerInternal.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.ClipData;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.display.DisplayManagerInternal;
@@ -140,6 +141,30 @@
     }
 
     /**
+     * An interface to customize drag and drop behaviors.
+     */
+    public interface IDragDropCallback {
+        /**
+         * Called when drag operation is started.
+         */
+        default boolean performDrag(IWindow window, IBinder dragToken,
+                int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+                ClipData data) {
+            return true;
+        }
+
+        /**
+         * Called when drop result is reported.
+         */
+        default void reportDropResult(IWindow window, boolean consumed) {}
+
+        /**
+         * Called when drag operation is cancelled.
+         */
+        default void cancelDragAndDrop(IBinder dragToken) {}
+    }
+
+    /**
      * Request that the window manager call
      * {@link DisplayManagerInternal#performTraversalInTransactionFromWindowManager}
      * within a surface transaction at a later time.
@@ -351,4 +376,9 @@
      * {@param vr2dDisplayId}.
      */
     public abstract void setVr2dDisplayId(int vr2dDisplayId);
+
+    /**
+     * Sets callback to DragDropController.
+     */
+    public abstract void registerDragDropControllerCallback(IDragDropCallback callback);
 }
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index e35bb92..aeb8489 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -133,27 +133,6 @@
     }
 
     /**
-     * Returns a {@link LinksInfo} that may be applied to the text to annotate it with links
-     * information.
-     *
-     * @param text the text to generate annotations for
-     * @param linkMask See {@link android.text.util.Linkify} for a list of linkMasks that may be
-     *      specified. Subclasses of this interface may specify additional linkMasks
-     * @param defaultLocales  ordered list of locale preferences that can be used to disambiguate
-     *      the provided text. If no locale preferences exist, set this to null or an empty locale
-     *      list in which case the classifier will decide whether to use no locale information, use
-     *      a default locale, or use the system default.
-     *
-     * @throws IllegalArgumentException if text is null
-     * @hide
-     */
-    @WorkerThread
-    default LinksInfo getLinks(
-            @NonNull CharSequence text, int linkMask, @Nullable LocaleList defaultLocales) {
-        return LinksInfo.NO_OP;
-    }
-
-    /**
      * Returns a {@link TextLinks} that may be applied to the text to annotate it with links
      * information.
      *
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 9c4fd57..107013a 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -30,13 +30,8 @@
 import android.provider.Browser;
 import android.provider.ContactsContract;
 import android.provider.Settings;
-import android.text.Spannable;
-import android.text.TextUtils;
-import android.text.method.WordIterator;
-import android.text.style.ClickableSpan;
 import android.text.util.Linkify;
 import android.util.Patterns;
-import android.view.View;
 import android.widget.TextViewMetrics;
 
 import com.android.internal.annotations.GuardedBy;
@@ -46,13 +41,8 @@
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.text.BreakIterator;
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -194,21 +184,6 @@
     }
 
     @Override
-    public LinksInfo getLinks(
-            @NonNull CharSequence text, int linkMask, @Nullable LocaleList defaultLocales) {
-        Preconditions.checkArgument(text != null);
-        try {
-            return LinksInfoFactory.create(
-                    mContext, getSmartSelection(defaultLocales), text.toString(), linkMask);
-        } catch (Throwable t) {
-            // Avoid throwing from this method. Log the error.
-            Log.e(LOG_TAG, "Error getting links info.", t);
-        }
-        // Getting here means something went wrong, return a NO_OP result.
-        return TextClassifier.NO_OP.getLinks(text, linkMask, defaultLocales);
-    }
-
-    @Override
     public TextLinks generateLinks(
             @NonNull CharSequence text, @Nullable TextLinks.Options options) {
         Preconditions.checkNotNull(text);
@@ -516,180 +491,6 @@
     }
 
     /**
-     * Detects and creates links for specified text.
-     */
-    private static final class LinksInfoFactory {
-
-        private LinksInfoFactory() {}
-
-        public static LinksInfo create(
-                Context context, SmartSelection smartSelection, String text, int linkMask) {
-            final WordIterator wordIterator = new WordIterator();
-            wordIterator.setCharSequence(text, 0, text.length());
-            final List<SpanSpec> spans = new ArrayList<>();
-            int start = 0;
-            int end;
-            while ((end = wordIterator.nextBoundary(start)) != BreakIterator.DONE) {
-                final String token = text.substring(start, end);
-                if (TextUtils.isEmpty(token)) {
-                    continue;
-                }
-
-                final int[] selection = smartSelection.suggest(text, start, end);
-                final int selectionStart = selection[0];
-                final int selectionEnd = selection[1];
-                if (selectionStart >= 0 && selectionEnd <= text.length()
-                        && selectionStart <= selectionEnd) {
-                    final SmartSelection.ClassificationResult[] results =
-                            smartSelection.classifyText(
-                                    text, selectionStart, selectionEnd,
-                                    getHintFlags(text, selectionStart, selectionEnd));
-                    if (results.length > 0) {
-                        final String type = getHighestScoringType(results);
-                        if (matches(type, linkMask)) {
-                            // For links without disambiguation, we simply use the default intent.
-                            final List<Intent> intents = IntentFactory.create(
-                                    context, type, text.substring(selectionStart, selectionEnd));
-                            if (!intents.isEmpty() && hasActivityHandler(context, intents.get(0))) {
-                                final ClickableSpan span = createSpan(context, intents.get(0));
-                                spans.add(new SpanSpec(selectionStart, selectionEnd, span));
-                            }
-                        }
-                    }
-                }
-                start = end;
-            }
-            return new LinksInfoImpl(text, avoidOverlaps(spans, text));
-        }
-
-        /**
-         * Returns true if the classification type matches the specified linkMask.
-         */
-        private static boolean matches(String type, int linkMask) {
-            type = type.trim().toLowerCase(Locale.ENGLISH);
-            if ((linkMask & Linkify.PHONE_NUMBERS) != 0
-                    && TextClassifier.TYPE_PHONE.equals(type)) {
-                return true;
-            }
-            if ((linkMask & Linkify.EMAIL_ADDRESSES) != 0
-                    && TextClassifier.TYPE_EMAIL.equals(type)) {
-                return true;
-            }
-            if ((linkMask & Linkify.MAP_ADDRESSES) != 0
-                    && TextClassifier.TYPE_ADDRESS.equals(type)) {
-                return true;
-            }
-            if ((linkMask & Linkify.WEB_URLS) != 0
-                    && TextClassifier.TYPE_URL.equals(type)) {
-                return true;
-            }
-            return false;
-        }
-
-        /**
-         * Trim the number of spans so that no two spans overlap.
-         *
-         * This algorithm first ensures that there is only one span per start index, then it
-         * makes sure that no two spans overlap.
-         */
-        private static List<SpanSpec> avoidOverlaps(List<SpanSpec> spans, String text) {
-            Collections.sort(spans, Comparator.comparingInt(span -> span.mStart));
-            // Group spans by start index. Take the longest span.
-            final Map<Integer, SpanSpec> reps = new LinkedHashMap<>();  // order matters.
-            final int size = spans.size();
-            for (int i = 0; i < size; i++) {
-                final SpanSpec span = spans.get(i);
-                final LinksInfoFactory.SpanSpec rep = reps.get(span.mStart);
-                if (rep == null || rep.mEnd < span.mEnd) {
-                    reps.put(span.mStart, span);
-                }
-            }
-            // Avoid span intersections. Take the longer span.
-            final LinkedList<SpanSpec> result = new LinkedList<>();
-            for (SpanSpec rep : reps.values()) {
-                if (result.isEmpty()) {
-                    result.add(rep);
-                    continue;
-                }
-
-                final SpanSpec last = result.getLast();
-                if (rep.mStart < last.mEnd) {
-                    // Spans intersect. Use the one with characters.
-                    if ((rep.mEnd - rep.mStart) > (last.mEnd - last.mStart)) {
-                        result.set(result.size() - 1, rep);
-                    }
-                } else {
-                    result.add(rep);
-                }
-            }
-            return result;
-        }
-
-        private static ClickableSpan createSpan(final Context context, final Intent intent) {
-            return new ClickableSpan() {
-                // TODO: Style this span.
-                @Override
-                public void onClick(View widget) {
-                    context.startActivity(intent);
-                }
-            };
-        }
-
-        private static boolean hasActivityHandler(Context context, Intent intent) {
-            if (intent == null) {
-                return false;
-            }
-            final ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent, 0);
-            return resolveInfo != null && resolveInfo.activityInfo != null;
-        }
-
-        /**
-         * Implementation of LinksInfo that adds ClickableSpans to the specified text.
-         */
-        private static final class LinksInfoImpl implements LinksInfo {
-
-            private final CharSequence mOriginalText;
-            private final List<SpanSpec> mSpans;
-
-            LinksInfoImpl(CharSequence originalText, List<SpanSpec> spans) {
-                mOriginalText = originalText;
-                mSpans = spans;
-            }
-
-            @Override
-            public boolean apply(@NonNull CharSequence text) {
-                Preconditions.checkArgument(text != null);
-                if (text instanceof Spannable && mOriginalText.toString().equals(text.toString())) {
-                    Spannable spannable = (Spannable) text;
-                    final int size = mSpans.size();
-                    for (int i = 0; i < size; i++) {
-                        final SpanSpec span = mSpans.get(i);
-                        spannable.setSpan(span.mSpan, span.mStart, span.mEnd, 0);
-                    }
-                    return true;
-                }
-                return false;
-            }
-        }
-
-        /**
-         * Span plus its start and end index.
-         */
-        private static final class SpanSpec {
-
-            private final int mStart;
-            private final int mEnd;
-            private final ClickableSpan mSpan;
-
-            SpanSpec(int start, int end, ClickableSpan span) {
-                mStart = start;
-                mEnd = end;
-                mSpan = span;
-            }
-        }
-    }
-
-    /**
      * Creates intents based on the classification type.
      */
     private static final class IntentFactory {
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 9295f5c..665d694 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -308,10 +308,15 @@
  * WebView may upload anonymous diagnostic data to Google when the user has consented. This data
  * helps Google improve WebView. Data is collected on a per-app basis for each app which has
  * instantiated a WebView. An individual app can opt out of this feature by putting the following
- * tag in its manifest:
+ * tag in its manifest's {@code <application>} element:
  * <pre>
- * &lt;meta-data android:name="android.webkit.WebView.MetricsOptOut"
- *            android:value="true" /&gt;
+ * &lt;manifest&gt;
+ *     &lt;application&gt;
+ *         ...
+ *         &lt;meta-data android:name=&quot;android.webkit.WebView.MetricsOptOut&quot;
+ *             android:value=&quot;true&quot; /&gt;
+ *     &lt;/application&gt;
+ * &lt;/manifest&gt;
  * </pre>
  * <p>
  * Data will only be uploaded for a given app if the user has consented AND the app has not opted
@@ -323,11 +328,17 @@
  * If Safe Browsing is enabled, WebView will block malicious URLs and present a warning UI to the
  * user to allow them to navigate back safely or proceed to the malicious page.
  * <p>
- * The recommended way for apps to enable the feature is putting the following tag in the manifest:
+ * The recommended way for apps to enable the feature is putting the following tag in the manifest's
+ * {@code <application>} element:
  * <p>
  * <pre>
- * &lt;meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
- *            android:value="true" /&gt;
+ * &lt;manifest&gt;
+ *     &lt;application&gt;
+ *         ...
+ *         &lt;meta-data android:name=&quot;android.webkit.WebView.EnableSafeBrowsing&quot;
+ *             android:value=&quot;true&quot; /&gt;
+ *     &lt;/application&gt;
+ * &lt;/manifest&gt;
  * </pre>
  *
  */
diff --git a/core/java/com/android/internal/app/NightDisplayController.java b/core/java/com/android/internal/app/ColorDisplayController.java
similarity index 98%
rename from core/java/com/android/internal/app/NightDisplayController.java
rename to core/java/com/android/internal/app/ColorDisplayController.java
index b8bfc64..b8682a8 100644
--- a/core/java/com/android/internal/app/NightDisplayController.java
+++ b/core/java/com/android/internal/app/ColorDisplayController.java
@@ -42,14 +42,14 @@
 import java.time.format.DateTimeParseException;
 
 /**
- * Controller for managing Night display settings.
+ * Controller for managing night display and color mode settings.
  * <p/>
  * Night display tints your screen red at night. This makes it easier to look at your screen in
  * dim light and may help you fall asleep more easily.
  */
-public final class NightDisplayController {
+public final class ColorDisplayController {
 
-    private static final String TAG = "NightDisplayController";
+    private static final String TAG = "ColorDisplayController";
     private static final boolean DEBUG = false;
 
     @Retention(RetentionPolicy.SOURCE)
@@ -114,11 +114,11 @@
 
     private Callback mCallback;
 
-    public NightDisplayController(@NonNull Context context) {
+    public ColorDisplayController(@NonNull Context context) {
         this(context, ActivityManager.getCurrentUser());
     }
 
-    public NightDisplayController(@NonNull Context context, int userId) {
+    public ColorDisplayController(@NonNull Context context, int userId) {
         mContext = context.getApplicationContext();
         mUserId = userId;
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/IOverviewProxy.aidl b/core/java/com/android/internal/net/INetworkWatchlistManager.aidl
similarity index 72%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/IOverviewProxy.aidl
copy to core/java/com/android/internal/net/INetworkWatchlistManager.aidl
index 8cf3be8..7e88369 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/IOverviewProxy.aidl
+++ b/core/java/com/android/internal/net/INetworkWatchlistManager.aidl
@@ -14,10 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.recents.model;
+package com.android.internal.net;
 
-import android.view.MotionEvent;
+import android.os.SharedMemory;
 
-oneway interface IOverviewProxy {
-    void onMotionEvent(in MotionEvent event);
+/** {@hide} */
+interface INetworkWatchlistManager {
+    boolean startWatchlistLogging();
+    boolean stopWatchlistLogging();
+    void reportWatchlistIfNecessary();
 }
diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java
index 3d3e148..5eda81b 100644
--- a/core/java/com/android/internal/net/NetworkStatsFactory.java
+++ b/core/java/com/android/internal/net/NetworkStatsFactory.java
@@ -43,6 +43,8 @@
 /**
  * Creates {@link NetworkStats} instances by parsing various {@code /proc/}
  * files as needed.
+ *
+ * @hide
  */
 public class NetworkStatsFactory {
     private static final String TAG = "NetworkStatsFactory";
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 49de135..7af1b46 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3657,7 +3657,7 @@
         </activity-alias>
         <activity-alias android:name="com.android.internal.app.ForwardIntentToManagedProfile"
                 android:targetActivity="com.android.internal.app.IntentForwarderActivity"
-                android:icon="@drawable/ic_corp_icon"
+                android:icon="@drawable/ic_corp_badge"
                 android:exported="true"
                 android:label="@string/managed_profile_label">
         </activity-alias>
@@ -3944,6 +3944,10 @@
         <service android:name="com.android.server.timezone.TimeZoneUpdateIdler"
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
+
+        <service android:name="com.android.server.net.watchlist.ReportWatchlistJobService"
+                 android:permission="android.permission.BIND_JOB_SERVICE" >
+        </service>
     </application>
 
 </manifest>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1c2e5a4..7cc43a7 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -876,7 +876,7 @@
     <bool name="config_nightDisplayAvailable">@bool/config_setColorTransformAccelerated</bool>
 
     <!-- Default mode to control how Night display is automatically activated.
-         One of the following values (see NightDisplayController.java):
+         One of the following values (see ColorDisplayController.java):
              0 - AUTO_MODE_DISABLED
              1 - AUTO_MODE_CUSTOM
              2 - AUTO_MODE_TWILIGHT
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 78a8e2a..9e0722b 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4744,4 +4744,7 @@
     A toast message shown when an app shortcut that wasn't restored due to an unknown issue is clicked,
     -->
     <string name="shortcut_restore_unknown_issue">Couldn\u2019t restore shortcut</string>
+
+    <!--Battery saver warning. STOPSHIP: Remove it eventually. -->
+    <string name="battery_saver_warning" translatable="false">Battery saver activated.\n\nSee go/extreme-battery-saver.\n\nThis contains aggressive experimental changes for P and may affect background app behavior.\n</string>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index fd0012d..d630839 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1324,6 +1324,7 @@
   <java-symbol type="drawable" name="cling_button" />
   <java-symbol type="drawable" name="cling_arrow_up" />
   <java-symbol type="drawable" name="cling_bg" />
+  <java-symbol type="drawable" name="ic_corp_badge" />
   <java-symbol type="drawable" name="ic_corp_badge_color" />
   <java-symbol type="drawable" name="ic_corp_badge_case" />
   <java-symbol type="drawable" name="ic_corp_icon" />
@@ -3144,6 +3145,7 @@
   <java-symbol type="string" name="shortcut_restore_not_supported" />
   <java-symbol type="string" name="shortcut_restore_signature_mismatch" />
   <java-symbol type="string" name="shortcut_restore_unknown_issue" />
+  <java-symbol type="string" name="battery_saver_warning" />
 
   <!-- From media projection -->
   <java-symbol type="string" name="config_mediaProjectionPermissionDialogComponent" />
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 1002939..d36ed63 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -26,7 +26,6 @@
 import static java.lang.reflect.Modifier.isPublic;
 import static java.lang.reflect.Modifier.isStatic;
 
-import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
@@ -264,6 +263,7 @@
                     Settings.Global.NETSTATS_UID_TAG_ROTATE_AGE,
                     Settings.Global.NETWORK_AVOID_BAD_WIFI,
                     Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE,
+                    Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME,
                     Settings.Global.NETWORK_PREFERENCE,
                     Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE,
                     Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS,
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index f7e0b46..161a0c2 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -258,6 +258,11 @@
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
     </privapp-permissions>
 
+    <privapp-permissions package="com.android.settings.intelligence">
+        <permission name="android.permission.READ_SEARCH_INDEXABLES"/>
+        <permission name="android.permission.MODIFY_PHONE_STATE"/>
+    </privapp-permissions>
+
     <privapp-permissions package="com.android.sharedstoragebackup">
         <permission name="android.permission.WRITE_MEDIA_STORAGE"/>
     </privapp-permissions>
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index d90386f..21861692 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -94,7 +94,7 @@
     public static Drawable getUserIcon(Context context, UserManager um, UserInfo user) {
         final int iconSize = UserIconDrawable.getSizeForList(context);
         if (user.isManagedProfile()) {
-            Drawable drawable = context.getDrawable(com.android.internal.R.drawable.ic_corp_icon);
+            Drawable drawable = context.getDrawable(com.android.internal.R.drawable.ic_corp_badge);
             drawable.setBounds(0, 0, iconSize, iconSize);
             return drawable;
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java b/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java
index 750601d..b27d823 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java
@@ -57,7 +57,7 @@
             if (userInfo.isManagedProfile()) {
                 mName = context.getString(R.string.managed_user_title);
                 icon = context.getDrawable(
-                    com.android.internal.R.drawable.ic_corp_icon);
+                    com.android.internal.R.drawable.ic_corp_badge);
             } else {
                 mName = userInfo.name;
                 final int userId = userInfo.id;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
similarity index 82%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/IOverviewProxy.aidl
rename to packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index 8cf3be8..173a90a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -14,10 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.recents.model;
+package com.android.systemui.shared.recents;
 
 import android.view.MotionEvent;
+import com.android.systemui.shared.recents.ISystemUiProxy;
 
 oneway interface IOverviewProxy {
+    void onBind(in ISystemUiProxy sysUiProxy);
     void onMotionEvent(in MotionEvent event);
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
new file mode 100644
index 0000000..d82b010
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.recents;
+
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+
+/**
+ * Temporary callbacks into SystemUI.
+ */
+interface ISystemUiProxy {
+
+    /**
+     * Proxies SurfaceControl.screenshot().
+     */
+    Bitmap screenshot(in Rect sourceCrop, int width, int height,
+                      int minLayer, int maxLayer, boolean useIdentityTransform,
+                      int rotation);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 62a9319..e7e70af 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -26,7 +26,7 @@
 import android.view.WindowManagerGlobal;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.NightDisplayController;
+import com.android.internal.app.ColorDisplayController;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.util.Preconditions;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -205,8 +205,8 @@
         mProviders.put(BatteryController.class, () ->
                 new BatteryControllerImpl(mContext));
 
-        mProviders.put(NightDisplayController.class, () ->
-                new NightDisplayController(mContext));
+        mProviders.put(ColorDisplayController.class, () ->
+                new ColorDisplayController(mContext));
 
         mProviders.put(ManagedProfileController.class, () ->
                 new ManagedProfileControllerImpl(mContext));
diff --git a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
index 528e242..9d960a1 100644
--- a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
@@ -20,12 +20,18 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Log;
-import com.android.systemui.shared.recents.model.IOverviewProxy;
+import android.view.SurfaceControl;
+
+import com.android.systemui.shared.recents.IOverviewProxy;
+import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 
@@ -46,6 +52,19 @@
     private IOverviewProxy mOverviewProxy;
     private int mConnectionBackoffAttempts;
 
+    private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
+        public Bitmap screenshot(Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
+                boolean useIdentityTransform, int rotation) {
+            long token = Binder.clearCallingIdentity();
+            try {
+                return SurfaceControl.screenshot(sourceCrop, width, height, minLayer, maxLayer,
+                        useIdentityTransform, rotation);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+    };
+
     private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() {
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
@@ -58,6 +77,11 @@
                 } catch (RemoteException e) {
                     Log.e(TAG, "Lost connection to launcher service", e);
                 }
+                try {
+                    mOverviewProxy.onBind(mSysUiProxy);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Failed to call onBind()", e);
+                }
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index 4c20361..763ffc6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -22,8 +22,7 @@
 import android.service.quicksettings.Tile;
 import android.widget.Switch;
 
-import com.android.internal.app.NightDisplayController;
-import com.android.internal.logging.MetricsLogger;
+import com.android.internal.app.ColorDisplayController;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.R;
 import com.android.systemui.qs.QSHost;
@@ -31,19 +30,19 @@
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 
 public class NightDisplayTile extends QSTileImpl<BooleanState>
-        implements NightDisplayController.Callback {
+        implements ColorDisplayController.Callback {
 
-    private NightDisplayController mController;
+    private ColorDisplayController mController;
     private boolean mIsListening;
 
     public NightDisplayTile(QSHost host) {
         super(host);
-        mController = new NightDisplayController(mContext, ActivityManager.getCurrentUser());
+        mController = new ColorDisplayController(mContext, ActivityManager.getCurrentUser());
     }
 
     @Override
     public boolean isAvailable() {
-        return NightDisplayController.isAvailable(mContext);
+        return ColorDisplayController.isAvailable(mContext);
     }
 
     @Override
@@ -65,7 +64,7 @@
         }
 
         // Make a new controller for the new user.
-        mController = new NightDisplayController(mContext, newUserId);
+        mController = new ColorDisplayController(mContext, newUserId);
         if (mIsListening) {
             mController.setListener(this);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index 1bd90fa..149ec0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -20,7 +20,7 @@
 import android.provider.Settings.Secure;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.NightDisplayController;
+import com.android.internal.app.ColorDisplayController;
 import com.android.systemui.Dependency;
 import com.android.systemui.Prefs;
 import com.android.systemui.Prefs.Key;
@@ -78,8 +78,8 @@
         }
 
         if (!mAutoTracker.isAdded(NIGHT)
-                && NightDisplayController.isAvailable(mContext)) {
-            Dependency.get(NightDisplayController.class).setListener(mNightDisplayCallback);
+                && ColorDisplayController.isAvailable(mContext)) {
+            Dependency.get(ColorDisplayController.class).setListener(mColorDisplayCallback);
         }
     }
 
@@ -89,7 +89,7 @@
         Dependency.get(HotspotController.class).removeCallback(mHotspotCallback);
         Dependency.get(DataSaverController.class).removeCallback(mDataSaverListener);
         Dependency.get(ManagedProfileController.class).removeCallback(mProfileCallback);
-        Dependency.get(NightDisplayController.class).setListener(null);
+        Dependency.get(ColorDisplayController.class).setListener(null);
     }
 
     private final ManagedProfileController.Callback mProfileCallback =
@@ -139,8 +139,8 @@
     };
 
     @VisibleForTesting
-    final NightDisplayController.Callback mNightDisplayCallback =
-            new NightDisplayController.Callback() {
+    final ColorDisplayController.Callback mColorDisplayCallback =
+            new ColorDisplayController.Callback() {
         @Override
         public void onActivated(boolean activated) {
             if (activated) {
@@ -150,8 +150,8 @@
 
         @Override
         public void onAutoModeChanged(int autoMode) {
-            if (autoMode == NightDisplayController.AUTO_MODE_CUSTOM
-                    || autoMode == NightDisplayController.AUTO_MODE_TWILIGHT) {
+            if (autoMode == ColorDisplayController.AUTO_MODE_CUSTOM
+                    || autoMode == ColorDisplayController.AUTO_MODE_TWILIGHT) {
                 addNightTile();
             }
         }
@@ -160,7 +160,7 @@
             if (mAutoTracker.isAdded(NIGHT)) return;
             mHost.addTile(NIGHT);
             mAutoTracker.setTileAdded(NIGHT);
-            mHandler.post(() -> Dependency.get(NightDisplayController.class)
+            mHandler.post(() -> Dependency.get(ColorDisplayController.class)
                     .setListener(null));
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
index 3ba9ac6..717699b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -35,7 +35,7 @@
 import com.android.systemui.R;
 import com.android.systemui.RecentsComponent;
 import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
-import com.android.systemui.shared.recents.model.IOverviewProxy;
+import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.tuner.TunerService;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index 3b401a5..a95e3db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -24,7 +24,7 @@
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
-import com.android.internal.app.NightDisplayController;
+import com.android.internal.app.ColorDisplayController;
 import com.android.systemui.Dependency;
 import com.android.systemui.Prefs;
 import com.android.systemui.Prefs.Key;
@@ -61,49 +61,49 @@
 
     @Test
     public void nightTileAdded_whenActivated() {
-        if (!NightDisplayController.isAvailable(mContext)) {
+        if (!ColorDisplayController.isAvailable(mContext)) {
             return;
         }
-        mAutoTileManager.mNightDisplayCallback.onActivated(true);
+        mAutoTileManager.mColorDisplayCallback.onActivated(true);
         verify(mQsTileHost).addTile("night");
     }
 
     @Test
     public void nightTileNotAdded_whenDeactivated() {
-        if (!NightDisplayController.isAvailable(mContext)) {
+        if (!ColorDisplayController.isAvailable(mContext)) {
             return;
         }
-        mAutoTileManager.mNightDisplayCallback.onActivated(false);
+        mAutoTileManager.mColorDisplayCallback.onActivated(false);
         verify(mQsTileHost, never()).addTile("night");
     }
 
     @Test
     public void nightTileAdded_whenNightModeTwilight() {
-        if (!NightDisplayController.isAvailable(mContext)) {
+        if (!ColorDisplayController.isAvailable(mContext)) {
             return;
         }
-        mAutoTileManager.mNightDisplayCallback.onAutoModeChanged(
-                NightDisplayController.AUTO_MODE_TWILIGHT);
+        mAutoTileManager.mColorDisplayCallback.onAutoModeChanged(
+                ColorDisplayController.AUTO_MODE_TWILIGHT);
         verify(mQsTileHost).addTile("night");
     }
 
     @Test
     public void nightTileAdded_whenNightModeCustom() {
-        if (!NightDisplayController.isAvailable(mContext)) {
+        if (!ColorDisplayController.isAvailable(mContext)) {
             return;
         }
-        mAutoTileManager.mNightDisplayCallback.onAutoModeChanged(
-                NightDisplayController.AUTO_MODE_CUSTOM);
+        mAutoTileManager.mColorDisplayCallback.onAutoModeChanged(
+                ColorDisplayController.AUTO_MODE_CUSTOM);
         verify(mQsTileHost).addTile("night");
     }
 
     @Test
     public void nightTileNotAdded_whenNightModeDisabled() {
-        if (!NightDisplayController.isAvailable(mContext)) {
+        if (!ColorDisplayController.isAvailable(mContext)) {
             return;
         }
-        mAutoTileManager.mNightDisplayCallback.onAutoModeChanged(
-                NightDisplayController.AUTO_MODE_DISABLED);
+        mAutoTileManager.mColorDisplayCallback.onAutoModeChanged(
+                ColorDisplayController.AUTO_MODE_DISABLED);
         verify(mQsTileHost, never()).addTile("night");
     }
 }
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index d3dcf5a..8e93d19 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -4680,6 +4680,16 @@
     // OS: P
     DIALOG_FIRMWARE_VERSION = 1247;
 
+    // OPEN: Settings > Network & internet > Menu > Private DNS
+    // CATEGORY: SETTINGS
+    // OS: P
+    DIALOG_PRIVATE_DNS = 1248;
+
+    // ACTION: A private dns mode been selected by user
+    // CATEGORY: SETTINGS
+    // OS: P
+    ACTION_PRIVATE_DNS_MODE = 1249;
+
     // Add new aosp constants above this line.
     // END OF AOSP CONSTANTS
   }
diff --git a/services/art-profile b/services/art-profile
index ac5ecaf..301918f 100644
--- a/services/art-profile
+++ b/services/art-profile
@@ -2374,6 +2374,9 @@
 HPLcom/android/server/display/AutomaticBrightnessController;->updateAmbientLux()V
 HPLcom/android/server/display/AutomaticBrightnessController;->updateAmbientLux(J)V
 HPLcom/android/server/display/AutomaticBrightnessController;->weightIntegral(J)F
+HPLcom/android/server/display/ColorDisplayService$3;->onAnimationUpdate(Landroid/animation/ValueAnimator;)V
+HPLcom/android/server/display/ColorDisplayService$ColorMatrixEvaluator;->evaluate(FLjava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HPLcom/android/server/display/ColorDisplayService$ColorMatrixEvaluator;->evaluate(F[F[F)[F
 HPLcom/android/server/display/DisplayBlanker;->requestDisplayState(II)V
 HPLcom/android/server/display/DisplayManagerService$BinderService;->getDisplayIds()[I
 HPLcom/android/server/display/DisplayManagerService$CallbackRecord;->binderDied()V
@@ -2430,9 +2433,6 @@
 HPLcom/android/server/display/LogicalDisplay;->getRequestedModeIdLocked()I
 HPLcom/android/server/display/LogicalDisplay;->hasContentLocked()Z
 HPLcom/android/server/display/LogicalDisplay;->setDisplayInfoOverrideFromWindowManagerLocked(Landroid/view/DisplayInfo;)Z
-HPLcom/android/server/display/NightDisplayService$3;->onAnimationUpdate(Landroid/animation/ValueAnimator;)V
-HPLcom/android/server/display/NightDisplayService$ColorMatrixEvaluator;->evaluate(FLjava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
-HPLcom/android/server/display/NightDisplayService$ColorMatrixEvaluator;->evaluate(F[F[F)[F
 HPLcom/android/server/display/RampAnimator$1;->run()V
 HPLcom/android/server/display/RampAnimator$Listener;->onAnimationEnd()V
 HPLcom/android/server/display/RampAnimator;->-get0(Lcom/android/server/display/RampAnimator;)F
@@ -10133,6 +10133,25 @@
 PLcom/android/server/display/AutomaticBrightnessController;->setLightSensorEnabled(Z)Z
 PLcom/android/server/display/AutomaticBrightnessController;->setScreenAutoBrightnessAdjustment(F)Z
 PLcom/android/server/display/AutomaticBrightnessController;->updateAutoBrightness(Z)V
+PLcom/android/server/display/ColorDisplayService$1;-><init>(Lcom/android/server/display/ColorDisplayService;)V
+PLcom/android/server/display/ColorDisplayService$3;-><init>(Lcom/android/server/display/ColorDisplayService;Lcom/android/server/display/DisplayTransformManager;)V
+PLcom/android/server/display/ColorDisplayService$4;-><init>(Lcom/android/server/display/ColorDisplayService;Lcom/android/server/display/DisplayTransformManager;[F)V
+PLcom/android/server/display/ColorDisplayService$4;->onAnimationCancel(Landroid/animation/Animator;)V
+PLcom/android/server/display/ColorDisplayService$4;->onAnimationEnd(Landroid/animation/Animator;)V
+PLcom/android/server/display/ColorDisplayService$ColorMatrixEvaluator;-><init>()V
+PLcom/android/server/display/ColorDisplayService$ColorMatrixEvaluator;-><init>(Lcom/android/server/display/ColorDisplayService$ColorMatrixEvaluator;)V
+PLcom/android/server/display/ColorDisplayService;->-set0(Lcom/android/server/display/ColorDisplayService;Landroid/animation/ValueAnimator;)Landroid/animation/ValueAnimator;
+PLcom/android/server/display/ColorDisplayService;-><init>(Landroid/content/Context;)V
+PLcom/android/server/display/ColorDisplayService;->applyTint(Z)V
+PLcom/android/server/display/ColorDisplayService;->isUserSetupCompleted(Landroid/content/ContentResolver;I)Z
+PLcom/android/server/display/ColorDisplayService;->onActivated(Z)V
+PLcom/android/server/display/ColorDisplayService;->onAutoModeChanged(I)V
+PLcom/android/server/display/ColorDisplayService;->onBootPhase(I)V
+PLcom/android/server/display/ColorDisplayService;->onStart()V
+PLcom/android/server/display/ColorDisplayService;->onStartUser(I)V
+PLcom/android/server/display/ColorDisplayService;->onUserChanged(I)V
+PLcom/android/server/display/ColorDisplayService;->setMatrix(I[F)V
+PLcom/android/server/display/ColorDisplayService;->setUp()V
 PLcom/android/server/display/ColorFade;-><init>(I)V
 PLcom/android/server/display/ColorFade;->createNativeFloatBuffer(I)Ljava/nio/FloatBuffer;
 PLcom/android/server/display/ColorFade;->dismiss()V
@@ -10230,25 +10249,6 @@
 PLcom/android/server/display/LogicalDisplay;->getDisplayIdLocked()I
 PLcom/android/server/display/LogicalDisplay;->getNonOverrideDisplayInfoLocked(Landroid/view/DisplayInfo;)V
 PLcom/android/server/display/LogicalDisplay;->setHasContentLocked(Z)V
-PLcom/android/server/display/NightDisplayService$1;-><init>(Lcom/android/server/display/NightDisplayService;)V
-PLcom/android/server/display/NightDisplayService$3;-><init>(Lcom/android/server/display/NightDisplayService;Lcom/android/server/display/DisplayTransformManager;)V
-PLcom/android/server/display/NightDisplayService$4;-><init>(Lcom/android/server/display/NightDisplayService;Lcom/android/server/display/DisplayTransformManager;[F)V
-PLcom/android/server/display/NightDisplayService$4;->onAnimationCancel(Landroid/animation/Animator;)V
-PLcom/android/server/display/NightDisplayService$4;->onAnimationEnd(Landroid/animation/Animator;)V
-PLcom/android/server/display/NightDisplayService$ColorMatrixEvaluator;-><init>()V
-PLcom/android/server/display/NightDisplayService$ColorMatrixEvaluator;-><init>(Lcom/android/server/display/NightDisplayService$ColorMatrixEvaluator;)V
-PLcom/android/server/display/NightDisplayService;->-set0(Lcom/android/server/display/NightDisplayService;Landroid/animation/ValueAnimator;)Landroid/animation/ValueAnimator;
-PLcom/android/server/display/NightDisplayService;-><init>(Landroid/content/Context;)V
-PLcom/android/server/display/NightDisplayService;->applyTint(Z)V
-PLcom/android/server/display/NightDisplayService;->isUserSetupCompleted(Landroid/content/ContentResolver;I)Z
-PLcom/android/server/display/NightDisplayService;->onActivated(Z)V
-PLcom/android/server/display/NightDisplayService;->onAutoModeChanged(I)V
-PLcom/android/server/display/NightDisplayService;->onBootPhase(I)V
-PLcom/android/server/display/NightDisplayService;->onStart()V
-PLcom/android/server/display/NightDisplayService;->onStartUser(I)V
-PLcom/android/server/display/NightDisplayService;->onUserChanged(I)V
-PLcom/android/server/display/NightDisplayService;->setMatrix(I[F)V
-PLcom/android/server/display/NightDisplayService;->setUp()V
 PLcom/android/server/display/OverlayDisplayAdapter$1$1;-><init>(Lcom/android/server/display/OverlayDisplayAdapter$1;Landroid/os/Handler;)V
 PLcom/android/server/display/OverlayDisplayAdapter$1;-><init>(Lcom/android/server/display/OverlayDisplayAdapter;)V
 PLcom/android/server/display/OverlayDisplayAdapter$1;->run()V
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index e609bd7..831c9cb 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1356,31 +1356,6 @@
         }
     }
 
-    public void notifyOemHookRawEventForSubscriber(int subId, byte[] rawData) {
-        if (!checkNotifyPermission("notifyOemHookRawEventForSubscriber")) {
-            return;
-        }
-
-        synchronized (mRecords) {
-            for (Record r : mRecords) {
-                if (VDBG) {
-                    log("notifyOemHookRawEventForSubscriber:  r=" + r + " subId=" + subId);
-                }
-                if ((r.matchPhoneStateListenerEvent(
-                        PhoneStateListener.LISTEN_OEM_HOOK_RAW_EVENT)) &&
-                        ((r.subId == subId) ||
-                        (r.subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID))) {
-                    try {
-                        r.callback.onOemHookRawEvent(rawData);
-                    } catch (RemoteException ex) {
-                        mRemoveList.add(r.binder);
-                    }
-                }
-            }
-            handleRemoveListLocked();
-        }
-    }
-
     @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
@@ -1673,11 +1648,6 @@
                     android.Manifest.permission.READ_PRECISE_PHONE_STATE, null);
 
         }
-
-        if ((events & PhoneStateListener.LISTEN_OEM_HOOK_RAW_EVENT) != 0) {
-            mContext.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null);
-        }
     }
 
     private void handleRemoveListLocked() {
diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java
index c04ddf8..ea7b443 100644
--- a/services/core/java/com/android/server/am/ActivityDisplay.java
+++ b/services/core/java/com/android/server/am/ActivityDisplay.java
@@ -404,8 +404,8 @@
 
         if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
                 || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
-            return supportsSplitScreen && WindowConfiguration.supportSplitScreenWindowingMode(
-                    windowingMode, activityType);
+            return supportsSplitScreen
+                    && WindowConfiguration.supportSplitScreenWindowingMode(activityType);
         }
 
         if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 88403fc..8cc584e 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -20,6 +20,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -870,6 +871,29 @@
         }
     }
 
+    /**
+     * @param reason The reason for moving the stack to the back.
+     * @param task If non-null, the task will be moved to the bottom of the stack.
+     **/
+    void moveToBack(String reason, TaskRecord task) {
+        if (!isAttached()) {
+            return;
+        }
+
+        getDisplay().positionChildAtBottom(this);
+        mStackSupervisor.setFocusStackUnchecked(reason, getDisplay().getTopStack());
+        if (task != null) {
+            insertTaskAtBottom(task);
+            return;
+        } else {
+            task = bottomTask();
+            if (task != null) {
+                mWindowContainerController.positionChildAtBottom(
+                        task.getWindowContainerController(), true /* includingParents */);
+            }
+        }
+    }
+
     boolean isFocusable() {
         if (getWindowConfiguration().canReceiveKeys()) {
             return true;
@@ -1778,6 +1802,15 @@
         return inPinnedWindowingMode();
     }
 
+    /** @return True if the resizing of the primary-split-screen stack affects this stack size. */
+    boolean affectedBySplitScreenResize() {
+        if (!supportsSplitScreenWindowingMode()) {
+            return false;
+        }
+        final int windowingMode = getWindowingMode();
+        return windowingMode != WINDOWING_MODE_FREEFORM && windowingMode != WINDOWING_MODE_PINNED;
+    }
+
     /**
      * @return the top most visible activity that wants to dismiss Keyguard
      */
@@ -2581,6 +2614,9 @@
         if (position >= mTaskHistory.size()) {
             insertTaskAtTop(task, null);
             return;
+        } else if (position <= 0) {
+            insertTaskAtBottom(task);
+            return;
         }
         position = getAdjustedPositionForTask(task, position, null /* starting */);
         mTaskHistory.remove(task);
@@ -2601,6 +2637,16 @@
                 true /* includingParents */);
     }
 
+    private void insertTaskAtBottom(TaskRecord task) {
+        // Unlike insertTaskAtPosition, this will also position parents of the windowcontroller.
+        mTaskHistory.remove(task);
+        final int position = getAdjustedPositionForTask(task, 0, null);
+        mTaskHistory.add(position, task);
+        updateTaskMovement(task, true);
+        mWindowContainerController.positionChildAtBottom(task.getWindowContainerController(),
+                true /* includingParents */);
+    }
+
     final void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
             boolean newTask, boolean keepCurTransition, ActivityOptions options) {
         TaskRecord rTask = r.getTask();
@@ -4370,8 +4416,7 @@
         updateTaskMovement(tr, false);
 
         mWindowManager.prepareAppTransition(TRANSIT_TASK_TO_BACK, false);
-        mWindowContainerController.positionChildAtBottom(tr.getWindowContainerController(),
-                true /* includingParents */);
+        moveToBack("moveTaskToBackLocked", tr);
 
         if (inPinnedWindowingMode()) {
             mStackSupervisor.removeStack(this);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index a0aeb63..062083c 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2441,7 +2441,7 @@
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeStack_" + stack.mStackId);
         mWindowManager.deferSurfaceLayout();
         try {
-            if (stack.supportsSplitScreenWindowingMode()) {
+            if (stack.affectedBySplitScreenResize()) {
                 if (bounds == null && stack.inSplitScreenWindowingMode()) {
                     // null bounds = fullscreen windowing mode...at least for now.
                     stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
@@ -2541,8 +2541,6 @@
                                 null, mTmpOptions, task, task.getActivityType(), onTop);
 
                     if (onTop) {
-                        final int returnToType =
-                                toDisplay.getTopVisibleStackActivityType(WINDOWING_MODE_PINNED);
                         final boolean isTopTask = i == (size - 1);
                         // Defer resume until all the tasks have been moved to the fullscreen stack
                         task.reparent(toStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT,
@@ -2631,7 +2629,7 @@
                     if (current.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                         continue;
                     }
-                    if (!current.supportsSplitScreenWindowingMode()) {
+                    if (!current.affectedBySplitScreenResize()) {
                         continue;
                     }
                     // Need to set windowing mode here before we try to get the dock bounds.
@@ -4177,8 +4175,8 @@
         }
 
         // Handle incorrect launch/move to secondary display if needed.
-        final boolean launchOnSecondaryDisplayFailed;
         if (isSecondaryDisplayPreferred) {
+            final boolean launchOnSecondaryDisplayFailed;
             final int actualDisplayId = task.getStack().mDisplayId;
             if (!task.canBeLaunchedOnDisplay(actualDisplayId)) {
                 // The task landed on an inappropriate display somehow, move it to the default
@@ -4193,34 +4191,34 @@
                         || (preferredDisplayId != INVALID_DISPLAY
                             && preferredDisplayId != actualDisplayId);
             }
-        } else {
-            // The task wasn't requested to be on a secondary display.
-            launchOnSecondaryDisplayFailed = false;
-        }
-
-        final ActivityRecord topActivity = task.getTopActivity();
-        if (launchOnSecondaryDisplayFailed
-                || !task.supportsSplitScreenWindowingMode() || forceNonResizable) {
             if (launchOnSecondaryDisplayFailed) {
                 // Display a warning toast that we tried to put a non-resizeable task on a secondary
                 // display with config different from global config.
                 mService.mTaskChangeNotificationController
                         .notifyActivityLaunchOnSecondaryDisplayFailed();
-            } else {
-                // Display a warning toast that we tried to put a non-dockable task in the docked
-                // stack.
-                mService.mTaskChangeNotificationController.notifyActivityDismissingDockedStack();
+                return;
             }
+        }
+
+        final ActivityRecord topActivity = task.getTopActivity();
+        if (!task.supportsSplitScreenWindowingMode() || forceNonResizable) {
+            // Display a warning toast that we tried to put a non-dockable task in the docked
+            // stack.
+            mService.mTaskChangeNotificationController.notifyActivityDismissingDockedStack();
 
             // Dismiss docked stack. If task appeared to be in docked stack but is not resizable -
             // we need to move it to top of fullscreen stack, otherwise it will be covered.
 
-            final ActivityStack dockedStack = task.getStack().getDisplay().getSplitScreenPrimaryStack();
+            final ActivityStack dockedStack =
+                    task.getStack().getDisplay().getSplitScreenPrimaryStack();
             if (dockedStack != null) {
                 moveTasksToFullscreenStackLocked(dockedStack, actualStack == dockedStack);
             }
-        } else if (topActivity != null && topActivity.isNonResizableOrForcedResizable()
-                && !topActivity.noDisplay) {
+            return;
+        }
+
+        if (topActivity != null && topActivity.isNonResizableOrForcedResizable()
+            && !topActivity.noDisplay) {
             final String packageName = topActivity.appInfo.packageName;
             final int reason = isSecondaryDisplayPreferred
                     ? FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY
@@ -4489,9 +4487,12 @@
                         "startActivityFromRecents: Task " + taskId + " not found.");
             }
 
-            // We always want to return to the home activity instead of the recents activity from
-            // whatever is started from the recents activity, so move the home stack forward.
-            moveHomeStackToFront("startActivityFromRecents");
+            if (windowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+                // We always want to return to the home activity instead of the recents activity
+                // from whatever is started from the recents activity, so move the home stack
+                // forward.
+                moveHomeStackToFront("startActivityFromRecents");
+            }
 
             // If the user must confirm credentials (e.g. when first launching a work app and the
             // Work Challenge is present) let startActivityInPackage handle the intercepting.
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
index 5cc390a..f427819 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
@@ -23,8 +23,6 @@
 import android.net.metrics.ApfProgramEvent;
 import android.net.metrics.IpConnectivityLog;
 import android.os.Binder;
-import android.os.IBinder;
-import android.os.Parcelable;
 import android.os.Process;
 import android.provider.Settings;
 import android.text.TextUtils;
@@ -322,22 +320,22 @@
         }
 
         @Override
-        public boolean registerNetdEventCallback(INetdEventCallback callback) {
+        public boolean addNetdEventCallback(int callerType, INetdEventCallback callback) {
             enforceNetdEventListeningPermission();
             if (mNetdListener == null) {
                 return false;
             }
-            return mNetdListener.registerNetdEventCallback(callback);
+            return mNetdListener.addNetdEventCallback(callerType, callback);
         }
 
         @Override
-        public boolean unregisterNetdEventCallback() {
+        public boolean removeNetdEventCallback(int callerType) {
             enforceNetdEventListeningPermission();
             if (mNetdListener == null) {
                 // if the service is null, we aren't registered anyway
                 return true;
             }
-            return mNetdListener.unregisterNetdEventCallback();
+            return mNetdListener.removeNetdEventCallback(callerType);
         }
     };
 
diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index 61b11e1..af138b93 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -98,21 +98,55 @@
     @GuardedBy("this")
     private final TokenBucket mConnectTb =
             new TokenBucket(CONNECT_LATENCY_FILL_RATE, CONNECT_LATENCY_BURST_LIMIT);
-    // Callback should only be registered/unregistered when logging is being enabled/disabled in DPM
-    // by the device owner. It's DevicePolicyManager's responsibility to ensure that.
-    @GuardedBy("this")
-    private INetdEventCallback mNetdEventCallback;
 
-    public synchronized boolean registerNetdEventCallback(INetdEventCallback callback) {
-        mNetdEventCallback = callback;
+
+    /**
+     * There are only 2 possible callbacks.
+     *
+     * mNetdEventCallbackList[CALLBACK_CALLER_DEVICE_POLICY].
+     * Callback registered/unregistered when logging is being enabled/disabled in DPM
+     * by the device owner. It's DevicePolicyManager's responsibility to ensure that.
+     *
+     * mNetdEventCallbackList[CALLBACK_CALLER_NETWORK_WATCHLIST]
+     * Callback registered/unregistered by NetworkWatchlistService.
+     */
+    @GuardedBy("this")
+    private static final int[] ALLOWED_CALLBACK_TYPES = {
+        INetdEventCallback.CALLBACK_CALLER_DEVICE_POLICY,
+        INetdEventCallback.CALLBACK_CALLER_NETWORK_WATCHLIST
+    };
+
+    @GuardedBy("this")
+    private INetdEventCallback[] mNetdEventCallbackList =
+            new INetdEventCallback[ALLOWED_CALLBACK_TYPES.length];
+
+    public synchronized boolean addNetdEventCallback(int callerType, INetdEventCallback callback) {
+        if (!isValidCallerType(callerType)) {
+            Log.e(TAG, "Invalid caller type: " + callerType);
+            return false;
+        }
+        mNetdEventCallbackList[callerType] = callback;
         return true;
     }
 
-    public synchronized boolean unregisterNetdEventCallback() {
-        mNetdEventCallback = null;
+    public synchronized boolean removeNetdEventCallback(int callerType) {
+        if (!isValidCallerType(callerType)) {
+            Log.e(TAG, "Invalid caller type: " + callerType);
+            return false;
+        }
+        mNetdEventCallbackList[callerType] = null;
         return true;
     }
 
+    private static boolean isValidCallerType(int callerType) {
+        for (int i = 0; i < ALLOWED_CALLBACK_TYPES.length; i++) {
+            if (callerType == ALLOWED_CALLBACK_TYPES[i]) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     public NetdEventListenerService(Context context) {
         this(context.getSystemService(ConnectivityManager.class));
     }
@@ -169,8 +203,10 @@
         long timestamp = System.currentTimeMillis();
         getMetricsForNetwork(timestamp, netId).addDnsResult(eventType, returnCode, latencyMs);
 
-        if (mNetdEventCallback != null) {
-            mNetdEventCallback.onDnsEvent(hostname, ipAddresses, ipAddressesCount, timestamp, uid);
+        for (INetdEventCallback callback : mNetdEventCallbackList) {
+            if (callback != null) {
+                callback.onDnsEvent(hostname, ipAddresses, ipAddressesCount, timestamp, uid);
+            }
         }
     }
 
@@ -184,8 +220,14 @@
         long timestamp = System.currentTimeMillis();
         getMetricsForNetwork(timestamp, netId).addConnectResult(error, latencyMs, ipAddr);
 
-        if (mNetdEventCallback != null) {
-            mNetdEventCallback.onConnectEvent(ipAddr, port, timestamp, uid);
+        for (INetdEventCallback callback : mNetdEventCallbackList) {
+            if (callback != null) {
+                // TODO(rickywai): Remove this checking to collect ip in watchlist.
+                if (callback ==
+                        mNetdEventCallbackList[INetdEventCallback.CALLBACK_CALLER_DEVICE_POLICY]) {
+                    callback.onConnectEvent(ipAddr, port, timestamp, uid);
+                }
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/display/NightDisplayService.java b/services/core/java/com/android/server/display/ColorDisplayService.java
similarity index 97%
rename from services/core/java/com/android/server/display/NightDisplayService.java
rename to services/core/java/com/android/server/display/ColorDisplayService.java
index a7c3ff9..af8ecad 100644
--- a/services/core/java/com/android/server/display/NightDisplayService.java
+++ b/services/core/java/com/android/server/display/ColorDisplayService.java
@@ -42,7 +42,7 @@
 import android.util.Slog;
 import android.view.animation.AnimationUtils;
 
-import com.android.internal.app.NightDisplayController;
+import com.android.internal.app.ColorDisplayController;
 import com.android.server.SystemService;
 import com.android.server.twilight.TwilightListener;
 import com.android.server.twilight.TwilightManager;
@@ -60,10 +60,10 @@
 /**
  * Tints the display at night.
  */
-public final class NightDisplayService extends SystemService
-        implements NightDisplayController.Callback {
+public final class ColorDisplayService extends SystemService
+        implements ColorDisplayController.Callback {
 
-    private static final String TAG = "NightDisplayService";
+    private static final String TAG = "ColorDisplayService";
 
     /**
      * The transition time, in milliseconds, for Night Display to turn on/off.
@@ -119,12 +119,12 @@
     private ContentObserver mUserSetupObserver;
     private boolean mBootCompleted;
 
-    private NightDisplayController mController;
+    private ColorDisplayController mController;
     private ValueAnimator mColorMatrixAnimator;
     private Boolean mIsActivated;
     private AutoMode mAutoMode;
 
-    public NightDisplayService(Context context) {
+    public ColorDisplayService(Context context) {
         super(context);
         mHandler = new Handler(Looper.getMainLooper());
     }
@@ -228,7 +228,7 @@
         Slog.d(TAG, "setUp: currentUser=" + mCurrentUser);
 
         // Create a new controller for the current user and start listening for changes.
-        mController = new NightDisplayController(getContext(), mCurrentUser);
+        mController = new ColorDisplayController(getContext(), mCurrentUser);
         mController.setListener(this);
 
         setCoefficientMatrix(getContext());
@@ -293,9 +293,9 @@
             mAutoMode = null;
         }
 
-        if (autoMode == NightDisplayController.AUTO_MODE_CUSTOM) {
+        if (autoMode == ColorDisplayController.AUTO_MODE_CUSTOM) {
             mAutoMode = new CustomAutoMode();
-        } else if (autoMode == NightDisplayController.AUTO_MODE_TWILIGHT) {
+        } else if (autoMode == ColorDisplayController.AUTO_MODE_TWILIGHT) {
             mAutoMode = new TwilightAutoMode();
         }
 
@@ -463,7 +463,7 @@
         return ldt.isBefore(compareTime) ? ldt.plusDays(1) : ldt;
     }
 
-    private abstract class AutoMode implements NightDisplayController.Callback {
+    private abstract class AutoMode implements ColorDisplayController.Callback {
         public abstract void onStart();
 
         public abstract void onStop();
diff --git a/services/core/java/com/android/server/display/DisplayTransformManager.java b/services/core/java/com/android/server/display/DisplayTransformManager.java
index bef6898..338e331 100644
--- a/services/core/java/com/android/server/display/DisplayTransformManager.java
+++ b/services/core/java/com/android/server/display/DisplayTransformManager.java
@@ -29,7 +29,7 @@
 import android.util.SparseArray;
 import com.android.internal.annotations.GuardedBy;
 
-import com.android.internal.app.NightDisplayController;
+import com.android.internal.app.ColorDisplayController;
 import java.util.Arrays;
 
 /**
@@ -223,13 +223,13 @@
     }
 
     public boolean setColorMode(int colorMode) {
-        if (colorMode == NightDisplayController.COLOR_MODE_NATURAL) {
+        if (colorMode == ColorDisplayController.COLOR_MODE_NATURAL) {
             applySaturation(COLOR_SATURATION_NATURAL);
             setNativeMode(false);
-        } else if (colorMode == NightDisplayController.COLOR_MODE_BOOSTED) {
+        } else if (colorMode == ColorDisplayController.COLOR_MODE_BOOSTED) {
             applySaturation(COLOR_SATURATION_BOOSTED);
             setNativeMode(false);
-        } else if (colorMode == NightDisplayController.COLOR_MODE_SATURATED) {
+        } else if (colorMode == ColorDisplayController.COLOR_MODE_SATURATED) {
             applySaturation(COLOR_SATURATION_NATURAL);
             setNativeMode(true);
         }
diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java
index 39f2a96..caa8522 100644
--- a/services/core/java/com/android/server/job/controllers/AppIdleController.java
+++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java
@@ -174,7 +174,7 @@
     private final class AppIdleStateChangeListener
             extends UsageStatsManagerInternal.AppIdleStateChangeListener {
         @Override
-        public void onAppIdleStateChanged(String packageName, int userId, boolean idle) {
+        public void onAppIdleStateChanged(String packageName, int userId, boolean idle, int bucket) {
             boolean changed = false;
             synchronized (mLock) {
                 if (mAppIdleParoleOn) {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 551cb10..3fa3cd4 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -3746,7 +3746,7 @@
             extends UsageStatsManagerInternal.AppIdleStateChangeListener {
 
         @Override
-        public void onAppIdleStateChanged(String packageName, int userId, boolean idle) {
+        public void onAppIdleStateChanged(String packageName, int userId, boolean idle, int bucket) {
             try {
                 final int uid = mContext.getPackageManager().getPackageUidAsUser(packageName,
                         PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
diff --git a/services/core/java/com/android/server/net/watchlist/DigestUtils.java b/services/core/java/com/android/server/net/watchlist/DigestUtils.java
new file mode 100644
index 0000000..57becb0
--- /dev/null
+++ b/services/core/java/com/android/server/net/watchlist/DigestUtils.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Utils for calculating digests.
+ */
+public class DigestUtils {
+
+    private static final int FILE_READ_BUFFER_SIZE = 16 * 1024;
+
+    private DigestUtils() {}
+
+    /** @return SHA256 hash of the provided file */
+    public static byte[] getSha256Hash(File apkFile) throws IOException, NoSuchAlgorithmException {
+        try (InputStream stream = new FileInputStream(apkFile)) {
+            return getSha256Hash(stream);
+        }
+    }
+
+    /** @return SHA256 hash of data read from the provided input stream */
+    public static byte[] getSha256Hash(InputStream stream)
+            throws IOException, NoSuchAlgorithmException {
+        MessageDigest digester = MessageDigest.getInstance("SHA256");
+
+        int bytesRead;
+        byte[] buf = new byte[FILE_READ_BUFFER_SIZE];
+        while ((bytesRead = stream.read(buf)) >= 0) {
+            digester.update(buf, 0, bytesRead);
+        }
+        return digester.digest();
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/net/watchlist/HarmfulDigests.java b/services/core/java/com/android/server/net/watchlist/HarmfulDigests.java
new file mode 100644
index 0000000..27c22ce
--- /dev/null
+++ b/services/core/java/com/android/server/net/watchlist/HarmfulDigests.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import com.android.internal.util.HexDump;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Helper class to store all harmful digests in memory.
+ * TODO: Optimize memory usage using byte array with binary search.
+ */
+class HarmfulDigests {
+
+    private final Set<String> mDigestSet;
+
+    HarmfulDigests(List<byte[]> digests) {
+        final HashSet<String> tmpDigestSet = new HashSet<>();
+        final int size = digests.size();
+        for (int i = 0; i < size; i++) {
+            tmpDigestSet.add(HexDump.toHexString(digests.get(i)));
+        }
+        mDigestSet = Collections.unmodifiableSet(tmpDigestSet);
+    }
+
+    public boolean contains(byte[] digest) {
+        return mDigestSet.contains(HexDump.toHexString(digest));
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        for (String digest : mDigestSet) {
+            pw.println(digest);
+        }
+        pw.println("");
+    }
+}
diff --git a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java
new file mode 100644
index 0000000..171703a
--- /dev/null
+++ b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.IIpConnectivityMetrics;
+import android.net.INetdEventCallback;
+import android.net.NetworkWatchlistManager;
+import android.net.metrics.IpConnectivityLog;
+import android.os.Binder;
+import android.os.Process;
+import android.os.SharedMemory;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.net.INetworkWatchlistManager;
+import com.android.server.ServiceThread;
+import com.android.server.SystemService;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Implementation of network watchlist service.
+ */
+public class NetworkWatchlistService extends INetworkWatchlistManager.Stub {
+
+    private static final String TAG = NetworkWatchlistService.class.getSimpleName();
+    static final boolean DEBUG = false;
+
+    private static final String PROPERTY_NETWORK_WATCHLIST_ENABLED =
+            "ro.network_watchlist_enabled";
+
+    private static final int MAX_NUM_OF_WATCHLIST_DIGESTS = 10000;
+
+    public static class Lifecycle extends SystemService {
+        private NetworkWatchlistService mService;
+
+        public Lifecycle(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onStart() {
+            if (!SystemProperties.getBoolean(PROPERTY_NETWORK_WATCHLIST_ENABLED, false)) {
+                // Watchlist service is disabled
+                return;
+            }
+            mService = new NetworkWatchlistService(getContext());
+            publishBinderService(Context.NETWORK_WATCHLIST_SERVICE, mService);
+        }
+
+        @Override
+        public void onBootPhase(int phase) {
+            if (!SystemProperties.getBoolean(PROPERTY_NETWORK_WATCHLIST_ENABLED, false)) {
+                // Watchlist service is disabled
+                return;
+            }
+            if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+                try {
+                    mService.initIpConnectivityMetrics();
+                    mService.startWatchlistLogging();
+                } catch (RemoteException e) {
+                    // Should not happen
+                }
+                ReportWatchlistJobService.schedule(getContext());
+            }
+        }
+    }
+
+    private volatile boolean mIsLoggingEnabled = false;
+    private final Object mLoggingSwitchLock = new Object();
+
+    private final WatchlistSettings mSettings;
+    private final Context mContext;
+
+    // Separate thread to handle expensive watchlist logging work.
+    private final ServiceThread mHandlerThread;
+
+    @VisibleForTesting
+    IIpConnectivityMetrics mIpConnectivityMetrics;
+    @VisibleForTesting
+    WatchlistLoggingHandler mNetworkWatchlistHandler;
+
+    public NetworkWatchlistService(Context context) {
+        mContext = context;
+        mSettings = WatchlistSettings.getInstance();
+        mHandlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
+                        /* allowIo */ false);
+        mHandlerThread.start();
+        mNetworkWatchlistHandler = new WatchlistLoggingHandler(mContext,
+                mHandlerThread.getLooper());
+        mNetworkWatchlistHandler.reportWatchlistIfNecessary();
+    }
+
+    // For testing only
+    @VisibleForTesting
+    NetworkWatchlistService(Context context, ServiceThread handlerThread,
+            WatchlistLoggingHandler handler, IIpConnectivityMetrics ipConnectivityMetrics) {
+        mContext = context;
+        mSettings = WatchlistSettings.getInstance();
+        mHandlerThread = handlerThread;
+        mNetworkWatchlistHandler = handler;
+        mIpConnectivityMetrics = ipConnectivityMetrics;
+    }
+
+    private void initIpConnectivityMetrics() {
+        mIpConnectivityMetrics = (IIpConnectivityMetrics) IIpConnectivityMetrics.Stub.asInterface(
+                ServiceManager.getService(IpConnectivityLog.SERVICE_NAME));
+    }
+
+    private final INetdEventCallback mNetdEventCallback = new INetdEventCallback.Stub() {
+        @Override
+        public void onDnsEvent(String hostname, String[] ipAddresses, int ipAddressesCount,
+                long timestamp, int uid) {
+            if (!mIsLoggingEnabled) {
+                return;
+            }
+            mNetworkWatchlistHandler.asyncNetworkEvent(hostname, ipAddresses, uid);
+        }
+
+        @Override
+        public void onConnectEvent(String ipAddr, int port, long timestamp, int uid) {
+            if (!mIsLoggingEnabled) {
+                return;
+            }
+            mNetworkWatchlistHandler.asyncNetworkEvent(null, new String[]{ipAddr}, uid);
+        }
+    };
+
+    @VisibleForTesting
+    protected boolean startWatchlistLoggingImpl() throws RemoteException {
+        if (DEBUG) {
+            Slog.i(TAG, "Starting watchlist logging.");
+        }
+        synchronized (mLoggingSwitchLock) {
+            if (mIsLoggingEnabled) {
+                Slog.w(TAG, "Watchlist logging is already running");
+                return true;
+            }
+            try {
+                if (mIpConnectivityMetrics.addNetdEventCallback(
+                        INetdEventCallback.CALLBACK_CALLER_NETWORK_WATCHLIST, mNetdEventCallback)) {
+                    mIsLoggingEnabled = true;
+                    return true;
+                } else {
+                    return false;
+                }
+            } catch (RemoteException re) {
+                // Should not happen
+                return false;
+            }
+        }
+    }
+
+    @Override
+    public boolean startWatchlistLogging() throws RemoteException {
+        enforceWatchlistLoggingPermission();
+        return startWatchlistLoggingImpl();
+    }
+
+    @VisibleForTesting
+    protected boolean stopWatchlistLoggingImpl() {
+        if (DEBUG) {
+            Slog.i(TAG, "Stopping watchlist logging");
+        }
+        synchronized (mLoggingSwitchLock) {
+            if (!mIsLoggingEnabled) {
+                Slog.w(TAG, "Watchlist logging is not running");
+                return true;
+            }
+            // stop the logging regardless of whether we fail to unregister listener
+            mIsLoggingEnabled = false;
+
+            try {
+                return mIpConnectivityMetrics.removeNetdEventCallback(
+                        INetdEventCallback.CALLBACK_CALLER_NETWORK_WATCHLIST);
+            } catch (RemoteException re) {
+                // Should not happen
+                return false;
+            }
+        }
+    }
+
+    @Override
+    public boolean stopWatchlistLogging() throws RemoteException {
+        enforceWatchlistLoggingPermission();
+        return stopWatchlistLoggingImpl();
+    }
+
+    private void enforceWatchlistLoggingPermission() {
+        final int uid = Binder.getCallingUid();
+        if (uid != Process.SYSTEM_UID) {
+            throw new SecurityException(String.format("Uid %d has no permission to change watchlist"
+                    + " setting.", uid));
+        }
+    }
+
+    /**
+     * Set a new network watchlist.
+     * This method should be called by ConfigUpdater only.
+     *
+     * @return True if network watchlist is updated.
+     */
+    public boolean setNetworkSecurityWatchlist(List<byte[]> domainsCrc32Digests,
+            List<byte[]> domainsSha256Digests,
+            List<byte[]> ipAddressesCrc32Digests,
+            List<byte[]> ipAddressesSha256Digests) {
+        Slog.i(TAG, "Setting network watchlist");
+        if (domainsCrc32Digests == null || domainsSha256Digests == null
+                || ipAddressesCrc32Digests == null || ipAddressesSha256Digests == null) {
+            Slog.e(TAG, "Parameters cannot be null");
+            return false;
+        }
+        if (domainsCrc32Digests.size() != domainsSha256Digests.size()
+                || ipAddressesCrc32Digests.size() != ipAddressesSha256Digests.size()) {
+            Slog.e(TAG, "Must need to have the same number of CRC32 and SHA256 digests");
+            return false;
+        }
+        if (domainsSha256Digests.size() + ipAddressesSha256Digests.size()
+                > MAX_NUM_OF_WATCHLIST_DIGESTS) {
+            Slog.e(TAG, "Total watchlist size cannot exceed " + MAX_NUM_OF_WATCHLIST_DIGESTS);
+            return false;
+        }
+        mSettings.writeSettingsToDisk(domainsCrc32Digests, domainsSha256Digests,
+                ipAddressesCrc32Digests, ipAddressesSha256Digests);
+        Slog.i(TAG, "Set network watchlist: Success");
+        return true;
+    }
+
+    @Override
+    public void reportWatchlistIfNecessary() {
+        // Allow any apps to trigger report event, as we won't run it if it's too early.
+        mNetworkWatchlistHandler.reportWatchlistIfNecessary();
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+        mSettings.dump(fd, pw, args);
+    }
+
+}
diff --git a/services/core/java/com/android/server/net/watchlist/ReportWatchlistJobService.java b/services/core/java/com/android/server/net/watchlist/ReportWatchlistJobService.java
new file mode 100644
index 0000000..dfeb1b2
--- /dev/null
+++ b/services/core/java/com/android/server/net/watchlist/ReportWatchlistJobService.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.net.NetworkWatchlistManager;
+import android.util.Slog;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A job that periodically report watchlist records.
+ */
+public class ReportWatchlistJobService extends JobService {
+
+    private static final boolean DEBUG = NetworkWatchlistService.DEBUG;
+    private static final String TAG = "WatchlistJobService";
+
+    // Unique job id used in system service, other jobs should not use the same value.
+    public static final int REPORT_WATCHLIST_RECORDS_JOB_ID = 0xd7689;
+    public static final long REPORT_WATCHLIST_RECORDS_PERIOD_MILLIS =
+            TimeUnit.HOURS.toMillis(12);
+
+    @Override
+    public boolean onStartJob(final JobParameters jobParameters) {
+        if (jobParameters.getJobId() != REPORT_WATCHLIST_RECORDS_JOB_ID) {
+            return false;
+        }
+        if (DEBUG) Slog.d(TAG, "Start scheduled job.");
+        new NetworkWatchlistManager(this).reportWatchlistIfNecessary();
+        jobFinished(jobParameters, false);
+        return true;
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters jobParameters) {
+        return true; // Reschedule when possible.
+    }
+
+    /**
+     * Schedule the {@link ReportWatchlistJobService} to run periodically.
+     */
+    public static void schedule(Context context) {
+        if (DEBUG) Slog.d(TAG, "Scheduling records aggregator task");
+        final JobScheduler scheduler =
+                (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+        scheduler.schedule(new JobInfo.Builder(REPORT_WATCHLIST_RECORDS_JOB_ID,
+                new ComponentName(context, ReportWatchlistJobService.class))
+                //.setOverrideDeadline(45 * 1000) // Schedule job soon, for testing.
+                .setPeriodic(REPORT_WATCHLIST_RECORDS_PERIOD_MILLIS)
+                .setRequiresDeviceIdle(true)
+                .setRequiresBatteryNotLow(true)
+                .setPersisted(false)
+                .build());
+    }
+
+}
diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java b/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java
new file mode 100644
index 0000000..2247558
--- /dev/null
+++ b/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
+import android.os.DropBoxManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A Handler class for network watchlist logging on a background thread.
+ */
+class WatchlistLoggingHandler extends Handler {
+
+    private static final String TAG = WatchlistLoggingHandler.class.getSimpleName();
+    private static final boolean DEBUG = NetworkWatchlistService.DEBUG;
+
+    @VisibleForTesting
+    static final int LOG_WATCHLIST_EVENT_MSG = 1;
+    @VisibleForTesting
+    static final int REPORT_RECORDS_IF_NECESSARY_MSG = 2;
+
+    private static final long ONE_DAY_MS = TimeUnit.DAYS.toMillis(1);
+    private static final String DROPBOX_TAG = "network_watchlist_report";
+
+    private final Context mContext;
+    private final ContentResolver mResolver;
+    private final PackageManager mPm;
+    private final WatchlistReportDbHelper mDbHelper;
+    private final WatchlistSettings mSettings;
+    // A cache for uid and apk digest mapping.
+    // As uid won't be reused until reboot, it's safe to assume uid is unique per signature and app.
+    // TODO: Use more efficient data structure.
+    private final HashMap<Integer, byte[]> mCachedUidDigestMap = new HashMap<>();
+
+    private interface WatchlistEventKeys {
+        String HOST = "host";
+        String IP_ADDRESSES = "ipAddresses";
+        String UID = "uid";
+        String TIMESTAMP = "timestamp";
+    }
+
+    WatchlistLoggingHandler(Context context, Looper looper) {
+        super(looper);
+        mContext = context;
+        mPm = mContext.getPackageManager();
+        mResolver = mContext.getContentResolver();
+        mDbHelper = WatchlistReportDbHelper.getInstance(context);
+        mSettings = WatchlistSettings.getInstance();
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case LOG_WATCHLIST_EVENT_MSG: {
+                final Bundle data = msg.getData();
+                handleNetworkEvent(
+                        data.getString(WatchlistEventKeys.HOST),
+                        data.getStringArray(WatchlistEventKeys.IP_ADDRESSES),
+                        data.getInt(WatchlistEventKeys.UID),
+                        data.getLong(WatchlistEventKeys.TIMESTAMP)
+                );
+                break;
+            }
+            case REPORT_RECORDS_IF_NECESSARY_MSG:
+                tryAggregateRecords();
+                break;
+            default: {
+                Slog.d(TAG, "WatchlistLoggingHandler received an unknown of message.");
+                break;
+            }
+        }
+    }
+
+    /**
+     * Report network watchlist records if we collected enough data.
+     */
+    public void reportWatchlistIfNecessary() {
+        final Message msg = obtainMessage(REPORT_RECORDS_IF_NECESSARY_MSG);
+        sendMessage(msg);
+    }
+
+    /**
+     * Insert network traffic event to watchlist async queue processor.
+     */
+    public void asyncNetworkEvent(String host, String[] ipAddresses, int uid) {
+        final Message msg = obtainMessage(LOG_WATCHLIST_EVENT_MSG);
+        final Bundle bundle = new Bundle();
+        bundle.putString(WatchlistEventKeys.HOST, host);
+        bundle.putStringArray(WatchlistEventKeys.IP_ADDRESSES, ipAddresses);
+        bundle.putInt(WatchlistEventKeys.UID, uid);
+        bundle.putLong(WatchlistEventKeys.TIMESTAMP, System.currentTimeMillis());
+        msg.setData(bundle);
+        sendMessage(msg);
+    }
+
+    private void handleNetworkEvent(String hostname, String[] ipAddresses,
+            int uid, long timestamp) {
+        if (DEBUG) {
+            Slog.i(TAG, "handleNetworkEvent with host: " + hostname + ", uid: " + uid);
+        }
+        final String cncDomain = searchAllSubDomainsInWatchlist(hostname);
+        if (cncDomain != null) {
+            insertRecord(getDigestFromUid(uid), cncDomain, timestamp);
+        } else {
+            final String cncIp = searchIpInWatchlist(ipAddresses);
+            if (cncIp != null) {
+                insertRecord(getDigestFromUid(uid), cncIp, timestamp);
+            }
+        }
+    }
+
+    private boolean insertRecord(byte[] digest, String cncHost, long timestamp) {
+        final boolean result = mDbHelper.insertNewRecord(digest, cncHost, timestamp);
+        tryAggregateRecords();
+        return result;
+    }
+
+    private boolean shouldReportNetworkWatchlist() {
+        final long lastReportTime = Settings.Global.getLong(mResolver,
+                Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME, 0L);
+        final long currentTimestamp = System.currentTimeMillis();
+        if (currentTimestamp < lastReportTime) {
+            Slog.i(TAG, "Last report time is larger than current time, reset report");
+            mDbHelper.cleanup();
+            return false;
+        }
+        return currentTimestamp >= lastReportTime + ONE_DAY_MS;
+    }
+
+    private void tryAggregateRecords() {
+        if (shouldReportNetworkWatchlist()) {
+            Slog.i(TAG, "Start aggregating watchlist records.");
+            final DropBoxManager dbox = mContext.getSystemService(DropBoxManager.class);
+            if (dbox != null && !dbox.isTagEnabled(DROPBOX_TAG)) {
+                final WatchlistReportDbHelper.AggregatedResult aggregatedResult =
+                        mDbHelper.getAggregatedRecords();
+                final byte[] encodedResult = encodeAggregatedResult(aggregatedResult);
+                if (encodedResult != null) {
+                    addEncodedReportToDropBox(encodedResult);
+                }
+            }
+            mDbHelper.cleanup();
+            Settings.Global.putLong(mResolver, Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME,
+                    System.currentTimeMillis());
+        } else {
+            Slog.i(TAG, "No need to aggregate record yet.");
+        }
+    }
+
+    private byte[] encodeAggregatedResult(
+            WatchlistReportDbHelper.AggregatedResult aggregatedResult) {
+        // TODO: Encode results using differential privacy.
+        return null;
+    }
+
+    private void addEncodedReportToDropBox(byte[] encodedReport) {
+        final DropBoxManager dbox = mContext.getSystemService(DropBoxManager.class);
+        dbox.addData(DROPBOX_TAG, encodedReport, 0);
+    }
+
+    /**
+     * Get app digest from app uid.
+     */
+    private byte[] getDigestFromUid(int uid) {
+        final byte[] cachedDigest = mCachedUidDigestMap.get(uid);
+        if (cachedDigest != null) {
+            return cachedDigest;
+        }
+        final String[] packageNames = mPm.getPackagesForUid(uid);
+        final int userId = UserHandle.getUserId(uid);
+        if (!ArrayUtils.isEmpty(packageNames)) {
+            for (String packageName : packageNames) {
+                try {
+                    final String apkPath = mPm.getPackageInfoAsUser(packageName,
+                            PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId)
+                            .applicationInfo.publicSourceDir;
+                    if (TextUtils.isEmpty(apkPath)) {
+                        Slog.w(TAG, "Cannot find apkPath for " + packageName);
+                        continue;
+                    }
+                    final byte[] digest = DigestUtils.getSha256Hash(new File(apkPath));
+                    mCachedUidDigestMap.put(uid, digest);
+                    return digest;
+                } catch (NameNotFoundException | NoSuchAlgorithmException | IOException e) {
+                    Slog.e(TAG, "Should not happen", e);
+                    return null;
+                }
+            }
+        } else {
+            Slog.e(TAG, "Should not happen");
+        }
+        return null;
+    }
+
+    /**
+     * Search if any ip addresses are in watchlist.
+     *
+     * @param ipAddresses Ip address that you want to search in watchlist.
+     * @return Ip address that exists in watchlist, null if it does not match anything.
+     */
+    private String searchIpInWatchlist(String[] ipAddresses) {
+        for (String ipAddress : ipAddresses) {
+            if (isIpInWatchlist(ipAddress)) {
+                return ipAddress;
+            }
+        }
+        return null;
+    }
+
+    /** Search if the ip is in watchlist */
+    private boolean isIpInWatchlist(String ipAddr) {
+        if (ipAddr == null) {
+            return false;
+        }
+        return mSettings.containsIp(ipAddr);
+    }
+
+    /** Search if the host is in watchlist */
+    private boolean isHostInWatchlist(String host) {
+        if (host == null) {
+            return false;
+        }
+        return mSettings.containsDomain(host);
+    }
+
+    /**
+     * Search if any sub-domain in host is in watchlist.
+     *
+     * @param host Host that we want to search.
+     * @return Domain that exists in watchlist, null if it does not match anything.
+     */
+    private String searchAllSubDomainsInWatchlist(String host) {
+        if (host == null) {
+            return null;
+        }
+        final String[] subDomains = getAllSubDomains(host);
+        for (String subDomain : subDomains) {
+            if (isHostInWatchlist(subDomain)) {
+                return subDomain;
+            }
+        }
+        return null;
+    }
+
+    /** Get all sub-domains in a host */
+    @VisibleForTesting
+    static String[] getAllSubDomains(String host) {
+        if (host == null) {
+            return null;
+        }
+        final ArrayList<String> subDomainList = new ArrayList<>();
+        subDomainList.add(host);
+        int index = host.indexOf(".");
+        while (index != -1) {
+            host = host.substring(index + 1);
+            if (!TextUtils.isEmpty(host)) {
+                subDomainList.add(host);
+            }
+            index = host.indexOf(".");
+        }
+        return subDomainList.toArray(new String[0]);
+    }
+}
diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java b/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java
new file mode 100644
index 0000000..f48463f
--- /dev/null
+++ b/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Pair;
+
+import com.android.internal.util.HexDump;
+
+import java.util.ArrayList;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Helper class to process watchlist read / save watchlist reports.
+ */
+class WatchlistReportDbHelper extends SQLiteOpenHelper {
+
+    private static final String TAG = "WatchlistReportDbHelper";
+
+    private static final String NAME = "watchlist_report.db";
+    private static final int VERSION = 2;
+
+    private static final int IDLE_CONNECTION_TIMEOUT_MS = 30000;
+
+    private static class WhiteListReportContract {
+        private static final String TABLE = "records";
+        private static final String APP_DIGEST = "app_digest";
+        private static final String CNC_DOMAIN = "cnc_domain";
+        private static final String TIMESTAMP = "timestamp";
+    }
+
+    private static final String CREATE_TABLE_MODEL = "CREATE TABLE "
+            + WhiteListReportContract.TABLE + "("
+            + WhiteListReportContract.APP_DIGEST + " BLOB,"
+            + WhiteListReportContract.CNC_DOMAIN + " TEXT,"
+            + WhiteListReportContract.TIMESTAMP + " INTEGER DEFAULT 0" + " )";
+
+    private static final int INDEX_DIGEST = 0;
+    private static final int INDEX_CNC_DOMAIN = 1;
+    private static final int INDEX_TIMESTAMP = 2;
+
+    private static final String[] DIGEST_DOMAIN_PROJECTION =
+            new String[] {
+                    WhiteListReportContract.APP_DIGEST,
+                    WhiteListReportContract.CNC_DOMAIN
+            };
+
+    private static WatchlistReportDbHelper sInstance;
+
+    /**
+     * Aggregated watchlist records.
+     */
+    public static class AggregatedResult {
+        // A list of digests that visited c&c domain or ip before.
+        Set<String> appDigestList;
+
+        // The c&c domain or ip visited before.
+        String cncDomainVisited;
+
+        // A list of app digests and c&c domain visited.
+        HashMap<String, String> appDigestCNCList;
+    }
+
+    private WatchlistReportDbHelper(Context context) {
+        super(context, WatchlistSettings.getSystemWatchlistFile(NAME).getAbsolutePath(),
+                null, VERSION);
+        // Memory optimization - close idle connections after 30s of inactivity
+        setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS);
+    }
+
+    public static synchronized WatchlistReportDbHelper getInstance(Context context) {
+        if (sInstance != null) {
+            return sInstance;
+        }
+        sInstance = new WatchlistReportDbHelper(context);
+        return sInstance;
+    }
+
+    @Override
+    public void onCreate(SQLiteDatabase db) {
+        db.execSQL(CREATE_TABLE_MODEL);
+    }
+
+    @Override
+    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+        // TODO: For now, drop older tables and recreate new ones.
+        db.execSQL("DROP TABLE IF EXISTS " + WhiteListReportContract.TABLE);
+        onCreate(db);
+    }
+
+    /**
+     * Insert new watchlist record.
+     *
+     * @param appDigest The digest of an app.
+     * @param cncDomain C&C domain that app visited.
+     * @return True if success.
+     */
+    public boolean insertNewRecord(byte[] appDigest, String cncDomain,
+            long timestamp) {
+        final SQLiteDatabase db = getWritableDatabase();
+        final ContentValues values = new ContentValues();
+        values.put(WhiteListReportContract.APP_DIGEST, appDigest);
+        values.put(WhiteListReportContract.CNC_DOMAIN, cncDomain);
+        values.put(WhiteListReportContract.TIMESTAMP, timestamp);
+        return db.insert(WhiteListReportContract.TABLE, null, values) != -1;
+    }
+
+    /**
+     * Aggregate the records in database, and return a rappor encoded result.
+     */
+    public AggregatedResult getAggregatedRecords() {
+        final long twoDaysBefore = getTwoDaysBeforeTimestamp();
+        final long yesterday = getYesterdayTimestamp();
+        final String selectStatement = WhiteListReportContract.TIMESTAMP + " >= ? AND " +
+                WhiteListReportContract.TIMESTAMP + " <= ?";
+
+        final SQLiteDatabase db = getReadableDatabase();
+        Cursor c = null;
+        try {
+            c = db.query(true /* distinct */,
+                    WhiteListReportContract.TABLE, DIGEST_DOMAIN_PROJECTION, selectStatement,
+                    new String[]{"" + twoDaysBefore, "" + yesterday}, null, null,
+                    null, null);
+            if (c == null || c.getCount() == 0) {
+                return null;
+            }
+            final AggregatedResult result = new AggregatedResult();
+            result.cncDomainVisited = null;
+            // After aggregation, each digest maximum will have only 1 record.
+            result.appDigestList = new HashSet<>();
+            result.appDigestCNCList = new HashMap<>();
+            while (c.moveToNext()) {
+                // We use hex string here as byte[] cannot be a key in HashMap.
+                String digestHexStr = HexDump.toHexString(c.getBlob(INDEX_DIGEST));
+                String cncDomain = c.getString(INDEX_CNC_DOMAIN);
+
+                result.appDigestList.add(digestHexStr);
+                if (result.cncDomainVisited != null) {
+                    result.cncDomainVisited = cncDomain;
+                }
+                result.appDigestCNCList.put(digestHexStr, cncDomain);
+            }
+            return result;
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+    }
+
+    /**
+     * Remove all the records before yesterday.
+     *
+     * @return True if success.
+     */
+    public boolean cleanup() {
+        final SQLiteDatabase db = getWritableDatabase();
+        final long twoDaysBefore = getTwoDaysBeforeTimestamp();
+        final String clause = WhiteListReportContract.TIMESTAMP + "< " + twoDaysBefore;
+        return db.delete(WhiteListReportContract.TABLE, clause, null) != 0;
+    }
+
+    static long getTwoDaysBeforeTimestamp() {
+        return getMidnightTimestamp(2);
+    }
+
+    static long getYesterdayTimestamp() {
+        return getMidnightTimestamp(1);
+    }
+
+    static long getMidnightTimestamp(int daysBefore) {
+        java.util.Calendar date = new GregorianCalendar();
+        // reset hour, minutes, seconds and millis
+        date.set(java.util.Calendar.HOUR_OF_DAY, 0);
+        date.set(java.util.Calendar.MINUTE, 0);
+        date.set(java.util.Calendar.SECOND, 0);
+        date.set(java.util.Calendar.MILLISECOND, 0);
+        date.add(java.util.Calendar.DAY_OF_MONTH, -daysBefore);
+        return date.getTimeInMillis();
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java b/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java
new file mode 100644
index 0000000..c50f0d5
--- /dev/null
+++ b/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import android.os.Environment;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.HexDump;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.CRC32;
+
+/**
+ * A util class to do watchlist settings operations, like setting watchlist, query if a domain
+ * exists in watchlist.
+ */
+class WatchlistSettings {
+    private static final String TAG = "WatchlistSettings";
+
+    // Settings xml will be stored in /data/system/network_watchlist/watchlist_settings.xml
+    static final String SYSTEM_WATCHLIST_DIR = "network_watchlist";
+
+    private static final String WATCHLIST_XML_FILE = "watchlist_settings.xml";
+
+    private static class XmlTags {
+        private static final String WATCHLIST_SETTINGS = "watchlist-settings";
+        private static final String SHA256_DOMAIN = "sha256-domain";
+        private static final String CRC32_DOMAIN = "crc32-domain";
+        private static final String SHA256_IP = "sha256-ip";
+        private static final String CRC32_IP = "crc32-ip";
+        private static final String HASH = "hash";
+    }
+
+    private static WatchlistSettings sInstance = new WatchlistSettings();
+    private final AtomicFile mXmlFile;
+    private final Object mLock = new Object();
+    private HarmfulDigests mCrc32DomainDigests = new HarmfulDigests(new ArrayList<>());
+    private HarmfulDigests mSha256DomainDigests = new HarmfulDigests(new ArrayList<>());
+    private HarmfulDigests mCrc32IpDigests = new HarmfulDigests(new ArrayList<>());
+    private HarmfulDigests mSha256IpDigests = new HarmfulDigests(new ArrayList<>());
+
+    public static synchronized WatchlistSettings getInstance() {
+        return sInstance;
+    }
+
+    private WatchlistSettings() {
+        this(getSystemWatchlistFile(WATCHLIST_XML_FILE));
+    }
+
+    @VisibleForTesting
+    protected WatchlistSettings(File xmlFile) {
+        mXmlFile = new AtomicFile(xmlFile);
+        readSettingsLocked();
+    }
+
+    static File getSystemWatchlistFile(String filename) {
+        final File dataSystemDir = Environment.getDataSystemDirectory();
+        final File systemWatchlistDir = new File(dataSystemDir, SYSTEM_WATCHLIST_DIR);
+        systemWatchlistDir.mkdirs();
+        return new File(systemWatchlistDir, filename);
+    }
+
+    private void readSettingsLocked() {
+        synchronized (mLock) {
+            FileInputStream stream;
+            try {
+                stream = mXmlFile.openRead();
+            } catch (FileNotFoundException e) {
+                Log.i(TAG, "No watchlist settings: " + mXmlFile.getBaseFile().getAbsolutePath());
+                return;
+            }
+
+            final List<byte[]> crc32DomainList = new ArrayList<>();
+            final List<byte[]> sha256DomainList = new ArrayList<>();
+            final List<byte[]> crc32IpList = new ArrayList<>();
+            final List<byte[]> sha256IpList = new ArrayList<>();
+
+            try {
+                XmlPullParser parser = Xml.newPullParser();
+                parser.setInput(stream, StandardCharsets.UTF_8.name());
+                parser.nextTag();
+                parser.require(XmlPullParser.START_TAG, null, XmlTags.WATCHLIST_SETTINGS);
+                while (parser.nextTag() == XmlPullParser.START_TAG) {
+                    String tagName = parser.getName();
+                    switch (tagName) {
+                        case XmlTags.CRC32_DOMAIN:
+                            parseHash(parser, tagName, crc32DomainList);
+                            break;
+                        case XmlTags.CRC32_IP:
+                            parseHash(parser, tagName, crc32IpList);
+                            break;
+                        case XmlTags.SHA256_DOMAIN:
+                            parseHash(parser, tagName, sha256DomainList);
+                            break;
+                        case XmlTags.SHA256_IP:
+                            parseHash(parser, tagName, sha256IpList);
+                            break;
+                        default:
+                            Log.w(TAG, "Unknown element: " + parser.getName());
+                            XmlUtils.skipCurrentTag(parser);
+                    }
+                }
+                parser.require(XmlPullParser.END_TAG, null, XmlTags.WATCHLIST_SETTINGS);
+                writeSettingsToMemory(crc32DomainList, sha256DomainList, crc32IpList, sha256IpList);
+            } catch (IllegalStateException | NullPointerException | NumberFormatException |
+                    XmlPullParserException | IOException | IndexOutOfBoundsException e) {
+                Log.w(TAG, "Failed parsing " + e);
+            } finally {
+                try {
+                    stream.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+    }
+
+    private void parseHash(XmlPullParser parser, String tagName, List<byte[]> hashSet)
+            throws IOException, XmlPullParserException {
+        parser.require(XmlPullParser.START_TAG, null, tagName);
+        while (parser.nextTag() == XmlPullParser.START_TAG) {
+            parser.require(XmlPullParser.START_TAG, null, XmlTags.HASH);
+            byte[] hash = HexDump.hexStringToByteArray(parser.nextText());
+            parser.require(XmlPullParser.END_TAG, null, XmlTags.HASH);
+            hashSet.add(hash);
+        }
+        parser.require(XmlPullParser.END_TAG, null, tagName);
+    }
+
+    /**
+     * Write network watchlist settings to disk.
+     * Adb should not use it, should use writeSettingsToMemory directly instead.
+     */
+    public void writeSettingsToDisk(List<byte[]> newCrc32DomainList,
+            List<byte[]> newSha256DomainList,
+            List<byte[]> newCrc32IpList,
+            List<byte[]> newSha256IpList) {
+        synchronized (mLock) {
+            FileOutputStream stream;
+            try {
+                stream = mXmlFile.startWrite();
+            } catch (IOException e) {
+                Log.w(TAG, "Failed to write display settings: " + e);
+                return;
+            }
+
+            try {
+                XmlSerializer out = new FastXmlSerializer();
+                out.setOutput(stream, StandardCharsets.UTF_8.name());
+                out.startDocument(null, true);
+                out.startTag(null, XmlTags.WATCHLIST_SETTINGS);
+
+                writeHashSetToXml(out, XmlTags.SHA256_DOMAIN, newSha256DomainList);
+                writeHashSetToXml(out, XmlTags.SHA256_IP, newSha256IpList);
+                writeHashSetToXml(out, XmlTags.CRC32_DOMAIN, newCrc32DomainList);
+                writeHashSetToXml(out, XmlTags.CRC32_IP, newCrc32IpList);
+
+                out.endTag(null, XmlTags.WATCHLIST_SETTINGS);
+                out.endDocument();
+                mXmlFile.finishWrite(stream);
+                writeSettingsToMemory(newCrc32DomainList, newSha256DomainList, newCrc32IpList,
+                        newSha256IpList);
+            } catch (IOException e) {
+                Log.w(TAG, "Failed to write display settings, restoring backup.", e);
+                mXmlFile.failWrite(stream);
+            }
+        }
+    }
+
+    /**
+     * Write network watchlist settings to memory.
+     */
+    public void writeSettingsToMemory(List<byte[]> newCrc32DomainList,
+            List<byte[]> newSha256DomainList,
+            List<byte[]> newCrc32IpList,
+            List<byte[]> newSha256IpList) {
+        synchronized (mLock) {
+            mCrc32DomainDigests = new HarmfulDigests(newCrc32DomainList);
+            mCrc32IpDigests = new HarmfulDigests(newCrc32IpList);
+            mSha256DomainDigests = new HarmfulDigests(newSha256DomainList);
+            mSha256IpDigests = new HarmfulDigests(newSha256IpList);
+        }
+    }
+
+    private static void writeHashSetToXml(XmlSerializer out, String tagName, List<byte[]> hashSet)
+            throws IOException {
+        out.startTag(null, tagName);
+        for (byte[] hash : hashSet) {
+            out.startTag(null, XmlTags.HASH);
+            out.text(HexDump.toHexString(hash));
+            out.endTag(null, XmlTags.HASH);
+        }
+        out.endTag(null, tagName);
+    }
+
+    public boolean containsDomain(String domain) {
+        // First it does a quick CRC32 check.
+        final byte[] crc32 = getCrc32(domain);
+        if (!mCrc32DomainDigests.contains(crc32)) {
+            return false;
+        }
+        // Now we do a slow SHA256 check.
+        final byte[] sha256 = getSha256(domain);
+        return mSha256DomainDigests.contains(sha256);
+    }
+
+    public boolean containsIp(String ip) {
+        // First it does a quick CRC32 check.
+        final byte[] crc32 = getCrc32(ip);
+        if (!mCrc32IpDigests.contains(crc32)) {
+            return false;
+        }
+        // Now we do a slow SHA256 check.
+        final byte[] sha256 = getSha256(ip);
+        return mSha256IpDigests.contains(sha256);
+    }
+
+
+    /** Get CRC32 of a string */
+    private byte[] getCrc32(String str) {
+        final CRC32 crc = new CRC32();
+        crc.update(str.getBytes());
+        final long tmp = crc.getValue();
+        return new byte[]{(byte)(tmp >> 24 & 255), (byte)(tmp >> 16 & 255),
+                (byte)(tmp >> 8 & 255), (byte)(tmp & 255)};
+    }
+
+    /** Get SHA256 of a string */
+    private byte[] getSha256(String str) {
+        MessageDigest messageDigest;
+        try {
+            messageDigest = MessageDigest.getInstance("SHA256");
+        } catch (NoSuchAlgorithmException e) {
+            /* can't happen */
+            return null;
+        }
+        messageDigest.update(str.getBytes());
+        return messageDigest.digest();
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("Domain CRC32 digest list:");
+        mCrc32DomainDigests.dump(fd, pw, args);
+        pw.println("Domain SHA256 digest list:");
+        mSha256DomainDigests.dump(fd, pw, args);
+        pw.println("Ip CRC32 digest list:");
+        mCrc32IpDigests.dump(fd, pw, args);
+        pw.println("Ip SHA256 digest list:");
+        mSha256IpDigests.dump(fd, pw, args);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
index cf9e6f3..19b0d9b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
@@ -52,16 +52,7 @@
     // Load the property for the given reason and check for validity. This will throw an
     // exception in case the reason or value are invalid.
     private static String getAndCheckValidity(int reason) {
-        String sysPropName = getSystemPropertyName(reason);
-        String sysPropValue;
-        // TODO: This is a temporary hack to keep marlin booting on aosp/master while we
-        // figure out how to deal with these system properties that currently appear on
-        // vendor.
-        if ("pm.dexopt.inactive".equals(sysPropName)) {
-            sysPropValue = "verify";
-        } else {
-            sysPropValue = SystemProperties.get(sysPropName);
-        }
+        String sysPropValue = SystemProperties.get(getSystemPropertyName(reason));
         if (sysPropValue == null || sysPropValue.isEmpty() ||
                 !DexFile.isValidCompilerFilter(sysPropValue)) {
             throw new IllegalStateException("Value \"" + sysPropValue +"\" not valid "
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 011e1ac..4b13404 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -66,6 +66,7 @@
 
     public static final Set<String> USER_RESTRICTIONS = newSetWithUniqueCheck(new String[] {
             UserManager.DISALLOW_CONFIG_WIFI,
+            UserManager.DISALLOW_CONFIG_LOCALE,
             UserManager.DISALLOW_MODIFY_ACCOUNTS,
             UserManager.DISALLOW_INSTALL_APPS,
             UserManager.DISALLOW_UNINSTALL_APPS,
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 7837940..caf85b0 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -8084,22 +8084,28 @@
     private int updateLightNavigationBarLw(int vis, WindowState opaque,
             WindowState opaqueOrDimming) {
         final WindowState imeWin = mWindowManagerFuncs.getInputMethodWindowLw();
-
-        final WindowState navColorWin;
-        if (imeWin != null && imeWin.isVisibleLw() && mNavigationBarPosition == NAV_BAR_BOTTOM) {
-            navColorWin = imeWin;
-        } else {
-            navColorWin = opaqueOrDimming;
+        final boolean isImeShownWithBottomNavBar =
+                imeWin != null && imeWin.isVisibleLw() && mNavigationBarPosition == NAV_BAR_BOTTOM;
+        if (isImeShownWithBottomNavBar) {
+            final int winFlags = PolicyControl.getWindowFlags(imeWin, null);
+            if ((winFlags & WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) {
+                // If the IME window is visible and explicitly requesting custom nav bar background
+                // rendering, respect its light flag.
+                vis &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+                vis |= PolicyControl.getSystemUiVisibility(imeWin, null)
+                        & View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+                return vis;
+            }
         }
 
-        if (navColorWin != null) {
-            if (navColorWin == opaque) {
-                // If the top fullscreen-or-dimming window is also the top fullscreen, respect
-                // its light flag.
+        if (opaqueOrDimming != null) {
+            if (opaqueOrDimming == opaque) {
+                // If the top fullscreen-or-dimming window is also the top fullscreen window,
+                // respect its light flag.
                 vis &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
-                vis |= PolicyControl.getSystemUiVisibility(navColorWin, null)
+                vis |= PolicyControl.getSystemUiVisibility(opaqueOrDimming, null)
                         & View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
-            } else if (navColorWin.isDimming() || navColorWin == imeWin) {
+            } else if (opaqueOrDimming.isDimming() || isImeShownWithBottomNavBar) {
                 // Otherwise if it's dimming or it's the IME window, clear the light flag.
                 vis &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
             }
diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java
index 273b945..0c73fe8 100644
--- a/services/core/java/com/android/server/power/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/BatterySaverPolicy.java
@@ -56,6 +56,9 @@
     private static final String KEY_ADJUST_BRIGHTNESS_FACTOR = "adjust_brightness_factor";
     private static final String KEY_FULLBACKUP_DEFERRED = "fullbackup_deferred";
     private static final String KEY_KEYVALUE_DEFERRED = "keyvaluebackup_deferred";
+    private static final String KEY_FORCE_ALL_APPS_STANDBY_JOBS = "force_all_apps_standby_jobs";
+    private static final String KEY_FORCE_ALL_APPS_STANDBY_ALARMS = "force_all_apps_standby_alarms";
+    private static final String KEY_OPTIONAL_SENSORS_DISABLED = "optional_sensors_disabled";
 
     private final KeyValueListParser mParser = new KeyValueListParser(',');
 
@@ -141,6 +144,21 @@
      */
     private float mAdjustBrightnessFactor;
 
+    /**
+     * Whether to put all apps in the stand-by mode or not for job scheduler.
+     */
+    private boolean mForceAllAppsStandbyJobs;
+
+    /**
+     * Whether to put all apps in the stand-by mode or not for alarms.
+     */
+    private boolean mForceAllAppsStandbyAlarms;
+
+    /**
+     * Weather to show non-essential sensors (e.g. edge sensors) or not.
+     */
+    private boolean mOptionalSensorsDisabled;
+
     private ContentResolver mContentResolver;
 
     public BatterySaverPolicy(Handler handler) {
@@ -180,10 +198,14 @@
             mAdjustBrightnessDisabled = mParser.getBoolean(KEY_ADJUST_BRIGHTNESS_DISABLED, false);
             mAdjustBrightnessFactor = mParser.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR, 0.5f);
             mDataSaverDisabled = mParser.getBoolean(KEY_DATASAVER_DISABLED, true);
+            mForceAllAppsStandbyJobs = mParser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY_JOBS, true);
+            mForceAllAppsStandbyAlarms =
+                    mParser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY_ALARMS, true);
+            mOptionalSensorsDisabled = mParser.getBoolean(KEY_OPTIONAL_SENSORS_DISABLED, true);
 
             // Get default value from Settings.Secure
             final int defaultGpsMode = Settings.Secure.getInt(mContentResolver, SECURE_KEY_GPS_MODE,
-                    GPS_MODE_DISABLED_WHEN_SCREEN_OFF);
+                    GPS_MODE_NO_CHANGE);
             mGpsMode = mParser.getInt(KEY_GPS_MODE, defaultGpsMode);
         }
     }
@@ -235,6 +257,15 @@
                 case ServiceType.VIBRATION:
                     return builder.setBatterySaverEnabled(mVibrationDisabled)
                             .build();
+                case ServiceType.FORCE_ALL_APPS_STANDBY_JOBS:
+                    return builder.setBatterySaverEnabled(mForceAllAppsStandbyJobs)
+                            .build();
+                case ServiceType.FORCE_ALL_APPS_STANDBY_ALARMS:
+                    return builder.setBatterySaverEnabled(mForceAllAppsStandbyAlarms)
+                            .build();
+                case ServiceType.OPTIONAL_SENSORS:
+                    return builder.setBatterySaverEnabled(mOptionalSensorsDisabled)
+                            .build();
                 default:
                     return builder.setBatterySaverEnabled(realMode)
                             .build();
@@ -259,6 +290,8 @@
         pw.println("  " + KEY_ADJUST_BRIGHTNESS_DISABLED + "=" + mAdjustBrightnessDisabled);
         pw.println("  " + KEY_ADJUST_BRIGHTNESS_FACTOR + "=" + mAdjustBrightnessFactor);
         pw.println("  " + KEY_GPS_MODE + "=" + mGpsMode);
-
+        pw.println("  " + KEY_FORCE_ALL_APPS_STANDBY_JOBS + "=" + mForceAllAppsStandbyJobs);
+        pw.println("  " + KEY_FORCE_ALL_APPS_STANDBY_ALARMS + "=" + mForceAllAppsStandbyAlarms);
+        pw.println("  " + KEY_OPTIONAL_SENSORS_DISABLED + "=" + mOptionalSensorsDisabled);
     }
 }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 416a606..a153fdf 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -71,6 +71,8 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 import android.view.WindowManagerPolicy;
+import android.widget.Toast;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.app.IBatteryStats;
@@ -1022,6 +1024,13 @@
                     intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
                     mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
                             Manifest.permission.DEVICE_POWER);
+
+                    // STOPSHIP Remove the toast.
+                    if (mLowPowerModeEnabled) {
+                        Toast.makeText(mContext,
+                                com.android.internal.R.string.battery_saver_warning,
+                                Toast.LENGTH_LONG).show();
+                    }
                 }
             });
         }
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 76e25ba..4567e10 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -21,6 +21,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
+import android.annotation.NonNull;
 import android.content.ClipData;
 import android.graphics.PixelFormat;
 import android.os.Binder;
@@ -35,6 +36,8 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.view.View;
+import android.view.WindowManagerInternal.IDragDropCallback;
+import com.android.internal.util.Preconditions;
 import com.android.server.input.InputWindowHandle;
 
 /**
@@ -61,6 +64,7 @@
 
     private WindowManagerService mService;
     private final Handler mHandler;
+
     /**
      * Lock to preserve the order of state updates.
      * The lock is used to process drag and drop state updates in order without having the window
@@ -94,6 +98,11 @@
      */
     private final Object mWriteLock = new Object();
 
+    /**
+     * Callback which is used to sync drag state with the vendor-specific code.
+     */
+    @NonNull private IDragDropCallback mCallback = new IDragDropCallback() {};
+
     boolean dragDropActiveLocked() {
         return mDragState != null;
     }
@@ -102,6 +111,13 @@
         return mDragState.getInputWindowHandle();
     }
 
+    void registerCallback(IDragDropCallback callback) {
+        Preconditions.checkNotNull(callback);
+        synchronized (mWriteLock) {
+            mCallback = callback;
+        }
+    }
+
     DragDropController(WindowManagerService service, Looper looper) {
         mService = service;
         mHandler = new DragHandler(service, looper);
@@ -169,6 +185,10 @@
         }
 
         synchronized (mWriteLock) {
+            if (!mCallback.performDrag(window, dragToken, touchSource, touchX, touchY, thumbCenterX,
+                    thumbCenterY, data)) {
+                return false;
+            }
             synchronized (mService.mWindowMap) {
                 if (mDragState == null) {
                     Slog.w(TAG_WM, "No drag prepared");
@@ -251,6 +271,7 @@
         }
 
         synchronized (mWriteLock) {
+            mCallback.reportDropResult(window, consumed);
             synchronized (mService.mWindowMap) {
                 if (mDragState == null) {
                     // Most likely the drop recipient ANRed and we ended the drag
@@ -288,6 +309,7 @@
         }
 
         synchronized (mWriteLock) {
+            mCallback.cancelDragAndDrop(dragToken);
             synchronized (mService.mWindowMap) {
                 if (mDragState == null) {
                     Slog.w(TAG_WM, "cancelDragAndDrop() without prepareDrag()");
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 6e89e3e..170feac 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -603,6 +603,14 @@
         } else {
             maxPosition = computeMaxPosition(maxPosition);
         }
+
+        // preserve POSITION_BOTTOM/POSITION_TOP positions if they are still valid.
+        if (targetPosition == POSITION_BOTTOM && minPosition == 0) {
+            return POSITION_BOTTOM;
+        } else if (targetPosition == POSITION_TOP
+                && maxPosition == (addingNew ? stackSize : stackSize - 1)) {
+            return POSITION_TOP;
+        }
         // Reset position based on minimum/maximum possible positions.
         return Math.min(Math.max(targetPosition, minPosition), maxPosition);
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d9ba7d5..35870b1 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -125,6 +125,7 @@
 import android.app.AppOpsManager;
 import android.app.IActivityManager;
 import android.content.BroadcastReceiver;
+import android.content.ClipData;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -7441,6 +7442,11 @@
                 mVr2dDisplayId = vr2dDisplayId;
             }
         }
+
+        @Override
+        public void registerDragDropControllerCallback(IDragDropCallback callback) {
+            mDragDropController.registerCallback(callback);
+        }
     }
 
     void registerAppFreezeListener(AppFreezeListener listener) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
index 0085931..0aaf32c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
@@ -107,7 +107,8 @@
             return false;
         }
         try {
-           if (mIpConnectivityMetrics.registerNetdEventCallback(mNetdEventCallback)) {
+           if (mIpConnectivityMetrics.addNetdEventCallback(
+                   INetdEventCallback.CALLBACK_CALLER_DEVICE_POLICY, mNetdEventCallback)) {
                 mHandlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
                         /* allowIo */ false);
                 mHandlerThread.start();
@@ -138,7 +139,8 @@
                 // logging is forcefully disabled even if unregistering fails
                 return true;
             }
-            return mIpConnectivityMetrics.unregisterNetdEventCallback();
+            return mIpConnectivityMetrics.removeNetdEventCallback(
+                    INetdEventCallback.CALLBACK_CALLER_DEVICE_POLICY);
         } catch (RemoteException re) {
             Slog.wtf(TAG, "Failed to make remote calls to unregister the callback", re);
             return true;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index f8bcb73..90df82a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -37,7 +37,6 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.Process;
-import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.StrictMode;
 import android.os.SystemClock;
@@ -52,7 +51,7 @@
 import android.view.WindowManager;
 
 import com.android.internal.R;
-import com.android.internal.app.NightDisplayController;
+import com.android.internal.app.ColorDisplayController;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.BinderInternal;
@@ -69,7 +68,7 @@
 import com.android.server.coverage.CoverageService;
 import com.android.server.devicepolicy.DevicePolicyManagerService;
 import com.android.server.display.DisplayManagerService;
-import com.android.server.display.NightDisplayService;
+import com.android.server.display.ColorDisplayService;
 import com.android.server.dreams.DreamManagerService;
 import com.android.server.emergency.EmergencyAffordanceService;
 import com.android.server.fingerprint.FingerprintService;
@@ -83,6 +82,7 @@
 import com.android.server.media.projection.MediaProjectionManagerService;
 import com.android.server.net.NetworkPolicyManagerService;
 import com.android.server.net.NetworkStatsService;
+import com.android.server.net.watchlist.NetworkWatchlistService;
 import com.android.server.notification.NotificationManagerService;
 import com.android.server.oemlock.OemLockService;
 import com.android.server.om.OverlayManagerService;
@@ -884,6 +884,10 @@
             mSystemServiceManager.startService(IpConnectivityMetrics.class);
             traceEnd();
 
+            traceBeginAndSlog("NetworkWatchlistService");
+            mSystemServiceManager.startService(NetworkWatchlistService.Lifecycle.class);
+            traceEnd();
+
             traceBeginAndSlog("PinnerService");
             mSystemServiceManager.startService(PinnerService.class);
             traceEnd();
@@ -1277,9 +1281,9 @@
             mSystemServiceManager.startService(TwilightService.class);
             traceEnd();
 
-            if (NightDisplayController.isAvailable(context)) {
+            if (ColorDisplayController.isAvailable(context)) {
                 traceBeginAndSlog("StartNightDisplay");
-                mSystemServiceManager.startService(NightDisplayService.class);
+                mSystemServiceManager.startService(ColorDisplayService.class);
                 traceEnd();
             }
 
diff --git a/services/tests/servicestests/assets/NetworkWatchlistTest/watchlist_settings_test1.xml b/services/tests/servicestests/assets/NetworkWatchlistTest/watchlist_settings_test1.xml
new file mode 100644
index 0000000..bb97e94
--- /dev/null
+++ b/services/tests/servicestests/assets/NetworkWatchlistTest/watchlist_settings_test1.xml
@@ -0,0 +1,27 @@
+<?xml version='1.0'?>
+<watchlist-settings>
+    <sha256-domain>
+        <!-- test-cc-domain.com -->
+        <hash>8E7DCD2AEB4F364358242BB3F403263E61E3B4AECE4E2500FF28BF32E52FF0F1</hash>
+        <!-- test-cc-match-sha256-only.com -->
+        <hash>F0905DA7549614957B449034C281EF7BDEFDBC2B6E050AD1E78D6DE18FBD0D5F</hash>
+    </sha256-domain>
+    <sha256-ip>
+        <!-- 127.0.0.2 -->
+        <hash>1EDD62868F2767A1FFF68DF0A4CB3C23448E45100715768DB9310B5E719536A1</hash>
+        <!-- 127.0.0.3, match in sha256 only -->
+        <hash>18DD41C9F2E8E4879A1575FB780514EF33CF6E1F66578C4AE7CCA31F49B9F2ED</hash>
+    </sha256-ip>
+    <crc32-domain>
+        <!-- test-cc-domain.com -->
+        <hash>6C67059D</hash>
+        <!-- test-cc-match-crc32-only.com -->
+        <hash>3DC775F8</hash>
+    </crc32-domain>
+    <crc32-ip>
+        <!-- 127.0.0.2 -->
+        <hash>4EBEB612</hash>
+        <!-- 127.0.0.4, match in crc32 only -->
+        <hash>A7DD1327</hash>
+    </crc32-ip>
+</watchlist-settings>
diff --git a/services/tests/servicestests/src/com/android/server/NightDisplayServiceTest.java b/services/tests/servicestests/src/com/android/server/ColorDisplayServiceTest.java
similarity index 96%
rename from services/tests/servicestests/src/com/android/server/NightDisplayServiceTest.java
rename to services/tests/servicestests/src/com/android/server/ColorDisplayServiceTest.java
index 3a92d63..46b364c 100644
--- a/services/tests/servicestests/src/com/android/server/NightDisplayServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ColorDisplayServiceTest.java
@@ -29,10 +29,10 @@
 import android.support.test.runner.AndroidJUnit4;
 import android.test.mock.MockContentResolver;
 
-import com.android.internal.app.NightDisplayController;
+import com.android.internal.app.ColorDisplayController;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.display.DisplayTransformManager;
-import com.android.server.display.NightDisplayService;
+import com.android.server.display.ColorDisplayService;
 import com.android.server.twilight.TwilightListener;
 import com.android.server.twilight.TwilightManager;
 import com.android.server.twilight.TwilightState;
@@ -55,15 +55,15 @@
 import static org.mockito.Mockito.doReturn;
 
 @RunWith(AndroidJUnit4.class)
-public class NightDisplayServiceTest {
+public class ColorDisplayServiceTest {
 
     private Context mContext;
     private int mUserId;
 
     private MockTwilightManager mTwilightManager;
 
-    private NightDisplayController mNightDisplayController;
-    private NightDisplayService mNightDisplayService;
+    private ColorDisplayController mColorDisplayController;
+    private ColorDisplayService mColorDisplayService;
 
     @Before
     public void setUp() {
@@ -85,8 +85,8 @@
         mTwilightManager = new MockTwilightManager();
         LocalServices.addService(TwilightManager.class, mTwilightManager);
 
-        mNightDisplayController = new NightDisplayController(mContext, mUserId);
-        mNightDisplayService = new NightDisplayService(mContext);
+        mColorDisplayController = new ColorDisplayController(mContext, mUserId);
+        mColorDisplayService = new ColorDisplayService(mContext);
     }
 
     @After
@@ -94,8 +94,8 @@
         LocalServices.removeServiceForTest(DisplayTransformManager.class);
         LocalServices.removeServiceForTest(TwilightManager.class);
 
-        mNightDisplayService = null;
-        mNightDisplayController = null;
+        mColorDisplayService = null;
+        mColorDisplayController = null;
 
         mTwilightManager = null;
 
@@ -902,9 +902,9 @@
      * @param endTimeOffset the offset relative to now to deactivate Night display (in minutes)
      */
     private void setAutoModeCustom(int startTimeOffset, int endTimeOffset) {
-        mNightDisplayController.setAutoMode(NightDisplayController.AUTO_MODE_CUSTOM);
-        mNightDisplayController.setCustomStartTime(getLocalTimeRelativeToNow(startTimeOffset));
-        mNightDisplayController.setCustomEndTime(getLocalTimeRelativeToNow(endTimeOffset));
+        mColorDisplayController.setAutoMode(ColorDisplayController.AUTO_MODE_CUSTOM);
+        mColorDisplayController.setCustomStartTime(getLocalTimeRelativeToNow(startTimeOffset));
+        mColorDisplayController.setCustomEndTime(getLocalTimeRelativeToNow(endTimeOffset));
     }
 
     /**
@@ -914,7 +914,7 @@
      * @param sunriseOffset the offset relative to now for sunrise (in minutes)
      */
     private void setAutoModeTwilight(int sunsetOffset, int sunriseOffset) {
-        mNightDisplayController.setAutoMode(NightDisplayController.AUTO_MODE_TWILIGHT);
+        mColorDisplayController.setAutoMode(ColorDisplayController.AUTO_MODE_TWILIGHT);
         mTwilightManager.setTwilightState(
                 getTwilightStateRelativeToNow(sunsetOffset, sunriseOffset));
     }
@@ -927,7 +927,7 @@
      * activated (in minutes)
      */
     private void setActivated(boolean activated, int lastActivatedTimeOffset) {
-        mNightDisplayController.setActivated(activated);
+        mColorDisplayController.setActivated(activated);
         Secure.putStringForUser(mContext.getContentResolver(),
                 Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
                 LocalDateTime.now().plusMinutes(lastActivatedTimeOffset).toString(),
@@ -935,7 +935,7 @@
     }
 
     /**
-     * Convenience method to start {@link #mNightDisplayService}.
+     * Convenience method to start {@link #mColorDisplayService}.
      */
     private void startService() {
         Secure.putIntForUser(mContext.getContentResolver(), Secure.USER_SETUP_COMPLETE, 1, mUserId);
@@ -943,9 +943,9 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             @Override
             public void run() {
-                mNightDisplayService.onStart();
-                mNightDisplayService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
-                mNightDisplayService.onStartUser(mUserId);
+                mColorDisplayService.onStart();
+                mColorDisplayService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+                mColorDisplayService.onStartUser(mUserId);
             }
         });
     }
@@ -957,7 +957,7 @@
      */
     private void assertActivated(boolean activated) {
         assertWithMessage("Invalid Night display activated state")
-                .that(mNightDisplayController.isActivated())
+                .that(mColorDisplayController.isActivated())
                 .isEqualTo(activated);
     }
 
@@ -988,21 +988,21 @@
         final LocalDateTime now = LocalDateTime.now();
         final ZoneId zoneId = ZoneId.systemDefault();
 
-        long sunsetMillis = NightDisplayService.getDateTimeBefore(sunset, now)
+        long sunsetMillis = ColorDisplayService.getDateTimeBefore(sunset, now)
                 .atZone(zoneId)
                 .toInstant()
                 .toEpochMilli();
-        long sunriseMillis = NightDisplayService.getDateTimeBefore(sunrise, now)
+        long sunriseMillis = ColorDisplayService.getDateTimeBefore(sunrise, now)
                 .atZone(zoneId)
                 .toInstant()
                 .toEpochMilli();
         if (sunsetMillis < sunriseMillis) {
-            sunsetMillis = NightDisplayService.getDateTimeAfter(sunset, now)
+            sunsetMillis = ColorDisplayService.getDateTimeAfter(sunset, now)
                     .atZone(zoneId)
                     .toInstant()
                     .toEpochMilli();
         } else {
-            sunriseMillis = NightDisplayService.getDateTimeAfter(sunrise, now)
+            sunriseMillis = ColorDisplayService.getDateTimeAfter(sunrise, now)
                     .atZone(zoneId)
                     .toInstant()
                     .toEpochMilli();
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 9d23fe9..6de3395 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -3193,7 +3193,7 @@
         // setUp() adds a secondary user for CALLER_USER_HANDLE. Remove it as otherwise the
         // feature is disabled because there are non-affiliated secondary users.
         getServices().removeUser(DpmMockContext.CALLER_USER_HANDLE);
-        when(getServices().iipConnectivityMetrics.registerNetdEventCallback(anyObject()))
+        when(getServices().iipConnectivityMetrics.addNetdEventCallback(anyInt(), anyObject()))
                 .thenReturn(true);
 
         // No logs were retrieved so far.
diff --git a/services/tests/servicestests/src/com/android/server/net/watchlist/HarmfulDigestsTests.java b/services/tests/servicestests/src/com/android/server/net/watchlist/HarmfulDigestsTests.java
new file mode 100644
index 0000000..a34f95e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/net/watchlist/HarmfulDigestsTests.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.HexDump;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+/**
+ * runtest frameworks-services -c com.android.server.net.watchlist.HarmfulDigestsTests
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class HarmfulDigestsTests {
+
+    private static final byte[] TEST_DIGEST_1 = HexDump.hexStringToByteArray("AAAAAA");
+    private static final byte[] TEST_DIGEST_2 = HexDump.hexStringToByteArray("BBBBBB");
+    private static final byte[] TEST_DIGEST_3 = HexDump.hexStringToByteArray("AAAABB");
+    private static final byte[] TEST_DIGEST_4 = HexDump.hexStringToByteArray("BBBBAA");
+
+    @Before
+    public void setUp() throws Exception {
+    }
+
+    @After
+    public void tearDown() throws Exception {
+    }
+
+    @Test
+    public void testHarmfulDigests_setAndContains() throws Exception {
+        HarmfulDigests harmfulDigests = new HarmfulDigests(
+                Arrays.asList(new byte[][] {TEST_DIGEST_1}));
+        assertTrue(harmfulDigests.contains(TEST_DIGEST_1));
+        assertFalse(harmfulDigests.contains(TEST_DIGEST_2));
+        assertFalse(harmfulDigests.contains(TEST_DIGEST_3));
+        assertFalse(harmfulDigests.contains(TEST_DIGEST_4));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/net/watchlist/NetworkWatchlistServiceTests.java b/services/tests/servicestests/src/com/android/server/net/watchlist/NetworkWatchlistServiceTests.java
new file mode 100644
index 0000000..ccd3cdd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/net/watchlist/NetworkWatchlistServiceTests.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.net.ConnectivityMetricsEvent;
+import android.net.IIpConnectivityMetrics;
+import android.net.INetdEventCallback;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.ServiceThread;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * runtest frameworks-services -c com.android.server.net.watchlist.NetworkWatchlistServiceTests
+ */
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class NetworkWatchlistServiceTests {
+
+    private static final long NETWOR_EVENT_TIMEOUT_SEC = 1;
+    private static final String TEST_HOST = "testhost.com";
+    private static final String TEST_IP = "7.6.8.9";
+    private static final String[] TEST_IPS =
+            new String[] {"1.2.3.4", "4.6.8.9", "2001:0db8:0001:0000:0000:0ab9:C0A8:0102"};
+
+    private static class TestHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case WatchlistLoggingHandler.LOG_WATCHLIST_EVENT_MSG:
+                    onLogEvent();
+                    break;
+                case WatchlistLoggingHandler.REPORT_RECORDS_IF_NECESSARY_MSG:
+                    onAggregateEvent();
+                    break;
+                default:
+                    fail("Unexpected message: " + msg.what);
+            }
+        }
+
+        public void onLogEvent() {}
+        public void onAggregateEvent() {}
+    }
+
+    private static class TestIIpConnectivityMetrics implements IIpConnectivityMetrics {
+
+        int counter = 0;
+        INetdEventCallback callback = null;
+
+        @Override
+        public IBinder asBinder() {
+            return null;
+        }
+
+        @Override
+        public int logEvent(ConnectivityMetricsEvent connectivityMetricsEvent)
+                    throws RemoteException {
+            return 0;
+        }
+
+        @Override
+        public boolean addNetdEventCallback(int callerType, INetdEventCallback callback) {
+            counter++;
+            this.callback = callback;
+            return true;
+        }
+
+        @Override
+        public boolean removeNetdEventCallback(int callerType) {
+            counter--;
+            return true;
+        }
+    };
+
+    ServiceThread mHandlerThread;
+    WatchlistLoggingHandler mWatchlistHandler;
+    NetworkWatchlistService mWatchlistService;
+
+    @Before
+    public void setUp() {
+        mHandlerThread = new ServiceThread("NetworkWatchlistServiceTests",
+                Process.THREAD_PRIORITY_BACKGROUND, /* allowIo */ false);
+        mHandlerThread.start();
+        mWatchlistHandler = new WatchlistLoggingHandler(InstrumentationRegistry.getContext(),
+                mHandlerThread.getLooper());
+        mWatchlistService = new NetworkWatchlistService(InstrumentationRegistry.getContext(),
+                mHandlerThread, mWatchlistHandler, null);
+    }
+
+    @After
+    public void tearDown() {
+        mHandlerThread.quitSafely();
+    }
+
+    @Test
+    public void testStartStopWatchlistLogging() throws Exception {
+        TestIIpConnectivityMetrics connectivityMetrics = new TestIIpConnectivityMetrics() {
+            @Override
+            public boolean addNetdEventCallback(int callerType, INetdEventCallback callback) {
+                super.addNetdEventCallback(callerType, callback);
+                assertEquals(callerType, INetdEventCallback.CALLBACK_CALLER_NETWORK_WATCHLIST);
+                return true;
+            }
+
+            @Override
+            public boolean removeNetdEventCallback(int callerType) {
+                super.removeNetdEventCallback(callerType);
+                assertEquals(callerType, INetdEventCallback.CALLBACK_CALLER_NETWORK_WATCHLIST);
+                return true;
+            }
+        };
+        assertEquals(connectivityMetrics.counter, 0);
+        mWatchlistService.mIpConnectivityMetrics = connectivityMetrics;
+        assertTrue(mWatchlistService.startWatchlistLoggingImpl());
+        assertEquals(connectivityMetrics.counter, 1);
+        assertTrue(mWatchlistService.startWatchlistLoggingImpl());
+        assertEquals(connectivityMetrics.counter, 1);
+        assertTrue(mWatchlistService.stopWatchlistLoggingImpl());
+        assertEquals(connectivityMetrics.counter, 0);
+        assertTrue(mWatchlistService.stopWatchlistLoggingImpl());
+        assertEquals(connectivityMetrics.counter, 0);
+        assertTrue(mWatchlistService.startWatchlistLoggingImpl());
+        assertEquals(connectivityMetrics.counter, 1);
+        assertTrue(mWatchlistService.stopWatchlistLoggingImpl());
+        assertEquals(connectivityMetrics.counter, 0);
+    }
+
+    @Test
+    public void testNetworkEvents() throws Exception {
+        TestIIpConnectivityMetrics connectivityMetrics = new TestIIpConnectivityMetrics();
+        mWatchlistService.mIpConnectivityMetrics = connectivityMetrics;
+        assertTrue(mWatchlistService.startWatchlistLoggingImpl());
+
+        // Test DNS events
+        final CountDownLatch testDnsLatch = new CountDownLatch(1);
+        final Object[] dnsParams = new Object[3];
+        final WatchlistLoggingHandler testDnsHandler =
+                new WatchlistLoggingHandler(InstrumentationRegistry.getContext(),
+                        mHandlerThread.getLooper()) {
+                    @Override
+                    public void asyncNetworkEvent(String host, String[] ipAddresses, int uid) {
+                        dnsParams[0] = host;
+                        dnsParams[1] = ipAddresses;
+                        dnsParams[2] = uid;
+                        testDnsLatch.countDown();
+                    }
+                };
+        mWatchlistService.mNetworkWatchlistHandler = testDnsHandler;
+        connectivityMetrics.callback.onDnsEvent(TEST_HOST, TEST_IPS, TEST_IPS.length, 123L, 456);
+        if (!testDnsLatch.await(NETWOR_EVENT_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+            fail("Timed out waiting for network event");
+        }
+        assertEquals(TEST_HOST, dnsParams[0]);
+        for (int i = 0; i < TEST_IPS.length; i++) {
+            assertEquals(TEST_IPS[i], ((String[])dnsParams[1])[i]);
+        }
+        assertEquals(456, dnsParams[2]);
+
+        // Test connect events
+        final CountDownLatch testConnectLatch = new CountDownLatch(1);
+        final Object[] connectParams = new Object[3];
+        final WatchlistLoggingHandler testConnectHandler =
+                new WatchlistLoggingHandler(InstrumentationRegistry.getContext(),
+                        mHandlerThread.getLooper()) {
+                    @Override
+                    public void asyncNetworkEvent(String host, String[] ipAddresses, int uid) {
+                        connectParams[0] = host;
+                        connectParams[1] = ipAddresses;
+                        connectParams[2] = uid;
+                        testConnectLatch.countDown();
+                    }
+                };
+        mWatchlistService.mNetworkWatchlistHandler = testConnectHandler;
+        connectivityMetrics.callback.onConnectEvent(TEST_IP, 80, 123L, 456);
+        if (!testConnectLatch.await(NETWOR_EVENT_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+            fail("Timed out waiting for network event");
+        }
+        assertNull(connectParams[0]);
+        assertEquals(1, ((String[]) connectParams[1]).length);
+        assertEquals(TEST_IP, ((String[]) connectParams[1])[0]);
+        assertEquals(456, connectParams[2]);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistLoggingHandlerTests.java b/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistLoggingHandlerTests.java
new file mode 100644
index 0000000..e356b13
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistLoggingHandlerTests.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+/**
+ * runtest frameworks-services -c com.android.server.net.watchlist.WatchlistLoggingHandlerTests
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class WatchlistLoggingHandlerTests {
+
+    @Before
+    public void setUp() throws Exception {
+    }
+
+    @After
+    public void tearDown() throws Exception {
+    }
+
+    @Test
+    public void testWatchlistLoggingHandler_getAllSubDomains() throws Exception {
+        String[] subDomains = WatchlistLoggingHandler.getAllSubDomains("abc.def.gh.i.jkl.mm");
+        assertTrue(Arrays.equals(subDomains, new String[] {"abc.def.gh.i.jkl.mm",
+                "def.gh.i.jkl.mm", "gh.i.jkl.mm", "i.jkl.mm", "jkl.mm", "mm"}));
+        subDomains = WatchlistLoggingHandler.getAllSubDomains(null);
+        assertNull(subDomains);
+        subDomains = WatchlistLoggingHandler.getAllSubDomains("jkl.mm");
+        assertTrue(Arrays.equals(subDomains, new String[] {"jkl.mm", "mm"}));
+        subDomains = WatchlistLoggingHandler.getAllSubDomains("abc");
+        assertTrue(Arrays.equals(subDomains, new String[] {"abc"}));
+        subDomains = WatchlistLoggingHandler.getAllSubDomains("jkl.mm.");
+        assertTrue(Arrays.equals(subDomains, new String[] {"jkl.mm.", "mm."}));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java b/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java
new file mode 100644
index 0000000..f3cb980
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.HexDump;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Arrays;
+
+/**
+ * runtest frameworks-services -c com.android.server.net.watchlist.WatchlistSettingsTests
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class WatchlistSettingsTests {
+
+    private static final String TEST_XML_1 = "NetworkWatchlistTest/watchlist_settings_test1.xml";
+    private static final String TEST_CC_DOMAIN = "test-cc-domain.com";
+    private static final String TEST_CC_IP = "127.0.0.2";
+    private static final String TEST_NOT_EXIST_CC_DOMAIN = "test-not-exist-cc-domain.com";
+    private static final String TEST_NOT_EXIST_CC_IP = "1.2.3.4";
+    private static final String TEST_SHA256_ONLY_DOMAIN = "test-cc-match-sha256-only.com";
+    private static final String TEST_SHA256_ONLY_IP = "127.0.0.3";
+    private static final String TEST_CRC32_ONLY_DOMAIN = "test-cc-match-crc32-only.com";
+    private static final String TEST_CRC32_ONLY_IP = "127.0.0.4";
+
+    private static final String TEST_NEW_CC_DOMAIN = "test-new-cc-domain.com";
+    private static final byte[] TEST_NEW_CC_DOMAIN_SHA256 = HexDump.hexStringToByteArray(
+            "B86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB43");
+    private static final byte[] TEST_NEW_CC_DOMAIN_CRC32 = HexDump.hexStringToByteArray("76795BD3");
+
+    private static final String TEST_NEW_CC_IP = "1.1.1.2";
+    private static final byte[] TEST_NEW_CC_IP_SHA256 = HexDump.hexStringToByteArray(
+            "721BAB5E313CF0CC76B10F9592F18B9D1B8996497501A3306A55B3AE9F1CC87C");
+    private static final byte[] TEST_NEW_CC_IP_CRC32 = HexDump.hexStringToByteArray("940B8BEE");
+
+    private Context mContext;
+    private File mTestXmlFile;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+        mTestXmlFile =  new File(mContext.getFilesDir(), "test_watchlist_settings.xml");
+        mTestXmlFile.delete();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mTestXmlFile.delete();
+    }
+
+    @Test
+    public void testWatchlistSettings_parsing() throws Exception {
+        copyWatchlistSettingsXml(mContext, TEST_XML_1, mTestXmlFile);
+        WatchlistSettings settings = new WatchlistSettings(mTestXmlFile);
+        assertTrue(settings.containsDomain(TEST_CC_DOMAIN));
+        assertTrue(settings.containsIp(TEST_CC_IP));
+        assertFalse(settings.containsDomain(TEST_NOT_EXIST_CC_DOMAIN));
+        assertFalse(settings.containsIp(TEST_NOT_EXIST_CC_IP));
+        assertFalse(settings.containsDomain(TEST_SHA256_ONLY_DOMAIN));
+        assertFalse(settings.containsIp(TEST_SHA256_ONLY_IP));
+        assertFalse(settings.containsDomain(TEST_CRC32_ONLY_DOMAIN));
+        assertFalse(settings.containsIp(TEST_CRC32_ONLY_IP));
+    }
+
+    @Test
+    public void testWatchlistSettings_writeSettingsToDisk() throws Exception {
+        copyWatchlistSettingsXml(mContext, TEST_XML_1, mTestXmlFile);
+        WatchlistSettings settings = new WatchlistSettings(mTestXmlFile);
+        settings.writeSettingsToDisk(Arrays.asList(TEST_NEW_CC_DOMAIN_CRC32),
+                Arrays.asList(TEST_NEW_CC_DOMAIN_SHA256), Arrays.asList(TEST_NEW_CC_IP_CRC32),
+                Arrays.asList(TEST_NEW_CC_IP_SHA256));
+        // Ensure old watchlist is not in memory
+        assertFalse(settings.containsDomain(TEST_CC_DOMAIN));
+        assertFalse(settings.containsIp(TEST_CC_IP));
+        assertFalse(settings.containsDomain(TEST_NOT_EXIST_CC_DOMAIN));
+        assertFalse(settings.containsIp(TEST_NOT_EXIST_CC_IP));
+        assertFalse(settings.containsDomain(TEST_SHA256_ONLY_DOMAIN));
+        assertFalse(settings.containsIp(TEST_SHA256_ONLY_IP));
+        assertFalse(settings.containsDomain(TEST_CRC32_ONLY_DOMAIN));
+        assertFalse(settings.containsIp(TEST_CRC32_ONLY_IP));
+        // Ensure new watchlist is in memory
+        assertTrue(settings.containsDomain(TEST_NEW_CC_DOMAIN));
+        assertTrue(settings.containsIp(TEST_NEW_CC_IP));
+        // Reload settings from disk and test again
+        settings = new WatchlistSettings(mTestXmlFile);
+        // Ensure old watchlist is not in memory
+        assertFalse(settings.containsDomain(TEST_CC_DOMAIN));
+        assertFalse(settings.containsIp(TEST_CC_IP));
+        assertFalse(settings.containsDomain(TEST_NOT_EXIST_CC_DOMAIN));
+        assertFalse(settings.containsIp(TEST_NOT_EXIST_CC_IP));
+        assertFalse(settings.containsDomain(TEST_SHA256_ONLY_DOMAIN));
+        assertFalse(settings.containsIp(TEST_SHA256_ONLY_IP));
+        assertFalse(settings.containsDomain(TEST_CRC32_ONLY_DOMAIN));
+        assertFalse(settings.containsIp(TEST_CRC32_ONLY_IP));
+        // Ensure new watchlist is in memory
+        assertTrue(settings.containsDomain(TEST_NEW_CC_DOMAIN));
+        assertTrue(settings.containsIp(TEST_NEW_CC_IP));
+    }
+
+    @Test
+    public void testWatchlistSettings_writeSettingsToMemory() throws Exception {
+        copyWatchlistSettingsXml(mContext, TEST_XML_1, mTestXmlFile);
+        WatchlistSettings settings = new WatchlistSettings(mTestXmlFile);
+        settings.writeSettingsToMemory(Arrays.asList(TEST_NEW_CC_DOMAIN_CRC32),
+                Arrays.asList(TEST_NEW_CC_DOMAIN_SHA256), Arrays.asList(TEST_NEW_CC_IP_CRC32),
+                Arrays.asList(TEST_NEW_CC_IP_SHA256));
+        // Ensure old watchlist is not in memory
+        assertFalse(settings.containsDomain(TEST_CC_DOMAIN));
+        assertFalse(settings.containsIp(TEST_CC_IP));
+        assertFalse(settings.containsDomain(TEST_NOT_EXIST_CC_DOMAIN));
+        assertFalse(settings.containsIp(TEST_NOT_EXIST_CC_IP));
+        assertFalse(settings.containsDomain(TEST_SHA256_ONLY_DOMAIN));
+        assertFalse(settings.containsIp(TEST_SHA256_ONLY_IP));
+        assertFalse(settings.containsDomain(TEST_CRC32_ONLY_DOMAIN));
+        assertFalse(settings.containsIp(TEST_CRC32_ONLY_IP));
+        // Ensure new watchlist is in memory
+        assertTrue(settings.containsDomain(TEST_NEW_CC_DOMAIN));
+        assertTrue(settings.containsIp(TEST_NEW_CC_IP));
+        // Reload settings from disk and test again
+        settings = new WatchlistSettings(mTestXmlFile);
+        // Ensure old watchlist is in memory
+        assertTrue(settings.containsDomain(TEST_CC_DOMAIN));
+        assertTrue(settings.containsIp(TEST_CC_IP));
+        assertFalse(settings.containsDomain(TEST_NOT_EXIST_CC_DOMAIN));
+        assertFalse(settings.containsIp(TEST_NOT_EXIST_CC_IP));
+        assertFalse(settings.containsDomain(TEST_SHA256_ONLY_DOMAIN));
+        assertFalse(settings.containsIp(TEST_SHA256_ONLY_IP));
+        assertFalse(settings.containsDomain(TEST_CRC32_ONLY_DOMAIN));
+        assertFalse(settings.containsIp(TEST_CRC32_ONLY_IP));
+        // Ensure new watchlist is not in memory
+        assertFalse(settings.containsDomain(TEST_NEW_CC_DOMAIN));
+        assertFalse(settings.containsIp(TEST_NEW_CC_IP));;
+    }
+
+    private static void copyWatchlistSettingsXml(Context context, String xmlAsset, File outFile)
+            throws IOException {
+        writeToFile(outFile, readAsset(context, xmlAsset));
+
+    }
+
+    private static String readAsset(Context context, String assetPath) throws IOException {
+        final StringBuilder sb = new StringBuilder();
+        try (BufferedReader br = new BufferedReader(
+                new InputStreamReader(
+                        context.getResources().getAssets().open(assetPath)))) {
+            String line;
+            while ((line = br.readLine()) != null) {
+                sb.append(line);
+                sb.append(System.lineSeparator());
+            }
+        }
+        return sb.toString();
+    }
+
+    private static void writeToFile(File path, String content)
+            throws IOException {
+        path.getParentFile().mkdirs();
+
+        try (FileWriter writer = new FileWriter(path)) {
+            writer.write(content);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
index 67ffe58..39d256a 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
@@ -16,16 +16,14 @@
 
 package com.android.server.usage;
 
-import static android.app.usage.AppStandby.REASON_TIMEOUT;
-import static android.app.usage.AppStandby.STANDBY_BUCKET_ACTIVE;
-import static android.app.usage.AppStandby.STANDBY_BUCKET_RARE;
-
 import android.app.usage.AppStandby;
 import android.os.FileUtils;
 import android.test.AndroidTestCase;
 
 import java.io.File;
 
+import static android.app.usage.AppStandby.*;
+
 public class AppIdleHistoryTests extends AndroidTestCase {
 
     File mStorageDir;
@@ -111,5 +109,9 @@
         assertEquals(aih.getAppStandbyBucket(PACKAGE_1, USER_ID, 5000), STANDBY_BUCKET_RARE);
         assertEquals(aih.getAppStandbyBucket(PACKAGE_2, USER_ID, 5000), STANDBY_BUCKET_ACTIVE);
         assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 5000), REASON_TIMEOUT);
+
+        assertTrue(aih.shouldInformListeners(PACKAGE_1, USER_ID, 5000, STANDBY_BUCKET_RARE));
+        assertFalse(aih.shouldInformListeners(PACKAGE_1, USER_ID, 5000, STANDBY_BUCKET_RARE));
+        assertTrue(aih.shouldInformListeners(PACKAGE_1, USER_ID, 5000, STANDBY_BUCKET_FREQUENT));
     }
 }
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 9846d6f..8531baf5 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -16,11 +16,7 @@
 
 package com.android.server.usage;
 
-import static android.app.usage.AppStandby.STANDBY_BUCKET_ACTIVE;
-import static android.app.usage.AppStandby.STANDBY_BUCKET_FREQUENT;
-import static android.app.usage.AppStandby.STANDBY_BUCKET_RARE;
-import static android.app.usage.AppStandby.STANDBY_BUCKET_WORKING_SET;
-
+import static android.app.usage.AppStandby.*;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -251,10 +247,22 @@
                         false));
     }
 
+    private void reportEvent(AppStandbyController controller, long elapsedTime) {
+        // Back to ACTIVE on event
+        UsageEvents.Event ev = new UsageEvents.Event();
+        ev.mPackage = PACKAGE_1;
+        ev.mEventType = UsageEvents.Event.USER_INTERACTION;
+        controller.reportEvent(ev, elapsedTime, USER_ID);
+    }
+
     @Test
     public void testBuckets() throws Exception {
         AppStandbyController controller = setupController();
 
+        assertTimeout(controller, 0, STANDBY_BUCKET_NEVER);
+
+        reportEvent(controller, 0);
+
         // ACTIVE bucket
         assertTimeout(controller, 11 * HOUR_MS, STANDBY_BUCKET_ACTIVE);
 
@@ -270,11 +278,7 @@
         // RARE bucket
         assertTimeout(controller, 9 * DAY_MS, STANDBY_BUCKET_RARE);
 
-        // Back to ACTIVE on event
-        UsageEvents.Event ev = new UsageEvents.Event();
-        ev.mPackage = PACKAGE_1;
-        ev.mEventType = UsageEvents.Event.USER_INTERACTION;
-        controller.reportEvent(ev, mInjector.mElapsedRealtime, USER_ID);
+        reportEvent(controller, 9 * DAY_MS);
 
         assertTimeout(controller, 9 * DAY_MS, STANDBY_BUCKET_ACTIVE);
 
@@ -287,6 +291,10 @@
         AppStandbyController controller = setupController();
         mInjector.setDisplayOn(false);
 
+        assertTimeout(controller, 0, STANDBY_BUCKET_NEVER);
+
+        reportEvent(controller, 0);
+
         // ACTIVE bucket
         assertTimeout(controller, 11 * HOUR_MS, STANDBY_BUCKET_ACTIVE);
 
diff --git a/services/usage/java/com/android/server/usage/AppIdleHistory.java b/services/usage/java/com/android/server/usage/AppIdleHistory.java
index e5d3915..c5ca330 100644
--- a/services/usage/java/com/android/server/usage/AppIdleHistory.java
+++ b/services/usage/java/com/android/server/usage/AppIdleHistory.java
@@ -103,7 +103,7 @@
         long lastUsedScreenTime;
         @StandbyBuckets int currentBucket;
         String bucketingReason;
-        int lastInformedState;
+        int lastInformedBucket;
     }
 
     AppIdleHistory(File storageDir, long elapsedRealtime) {
@@ -333,13 +333,12 @@
     }
 
     boolean shouldInformListeners(String packageName, int userId,
-            long elapsedRealtime, boolean isIdle) {
+            long elapsedRealtime, int bucket) {
         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
         AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
                 elapsedRealtime, true);
-        int targetState = isIdle? STATE_IDLE : STATE_ACTIVE;
-        if (appUsageHistory.lastInformedState != (isIdle ? STATE_IDLE : STATE_ACTIVE)) {
-            appUsageHistory.lastInformedState = targetState;
+        if (appUsageHistory.lastInformedBucket != bucket) {
+            appUsageHistory.lastInformedBucket = bucket;
             return true;
         }
         return false;
diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java
index 17fde57..5623a68 100644
--- a/services/usage/java/com/android/server/usage/AppStandbyController.java
+++ b/services/usage/java/com/android/server/usage/AppStandbyController.java
@@ -160,7 +160,6 @@
     private final Context mContext;
 
     // TODO: Provide a mechanism to set an external bucketing service
-    private boolean mUseInternalBucketingHeuristics = true;
 
     private AppWidgetManager mAppWidgetManager;
     private PowerManager mPowerManager;
@@ -367,29 +366,33 @@
                     Slog.d(TAG, "   Checking idle state for " + packageName);
                 }
                 if (isSpecial) {
-                    maybeInformListeners(packageName, userId, elapsedRealtime, false);
-                } else if (mUseInternalBucketingHeuristics) {
+                    maybeInformListeners(packageName, userId, elapsedRealtime,
+                            AppStandby.STANDBY_BUCKET_ACTIVE);
+                } else {
                     synchronized (mAppIdleLock) {
-                        int oldBucket = mAppIdleHistory.getAppStandbyBucket(packageName, userId,
-                                elapsedRealtime);
                         String bucketingReason = mAppIdleHistory.getAppStandbyReason(packageName,
                                 userId, elapsedRealtime);
-                        if (bucketingReason != null
-                                && (bucketingReason.equals(AppStandby.REASON_FORCED)
-                                    || bucketingReason.startsWith(AppStandby.REASON_PREDICTED))) {
+                        // If the bucket was forced by the developer, leave it alone
+                        if (AppStandby.REASON_FORCED.equals(bucketingReason)) {
                             continue;
                         }
-                        int newBucket = getBucketForLocked(packageName, userId,
-                                            elapsedRealtime);
-                        if (DEBUG) {
-                            Slog.d(TAG, "     Old bucket=" + oldBucket
-                                    + ", newBucket=" + newBucket);
-                        }
-                        if (oldBucket != newBucket) {
-                            mAppIdleHistory.setAppStandbyBucket(packageName, userId,
-                                    elapsedRealtime, newBucket, AppStandby.REASON_TIMEOUT);
-                            maybeInformListeners(packageName, userId, elapsedRealtime,
-                                    newBucket >= AppStandby.STANDBY_BUCKET_RARE);
+                        // If the bucket was moved up due to usage, let the timeouts apply.
+                        if (AppStandby.REASON_USAGE.equals(bucketingReason)
+                                || AppStandby.REASON_TIMEOUT.equals(bucketingReason)) {
+                            int oldBucket = mAppIdleHistory.getAppStandbyBucket(packageName, userId,
+                                    elapsedRealtime);
+                            int newBucket = getBucketForLocked(packageName, userId,
+                                    elapsedRealtime);
+                            if (DEBUG) {
+                                Slog.d(TAG, "     Old bucket=" + oldBucket
+                                        + ", newBucket=" + newBucket);
+                            }
+                            if (oldBucket < newBucket) {
+                                mAppIdleHistory.setAppStandbyBucket(packageName, userId,
+                                        elapsedRealtime, newBucket, AppStandby.REASON_TIMEOUT);
+                                maybeInformListeners(packageName, userId, elapsedRealtime,
+                                        newBucket);
+                            }
                         }
                     }
                 }
@@ -403,12 +406,12 @@
     }
 
     private void maybeInformListeners(String packageName, int userId,
-            long elapsedRealtime, boolean isIdle) {
+            long elapsedRealtime, int bucket) {
         synchronized (mAppIdleLock) {
             if (mAppIdleHistory.shouldInformListeners(packageName, userId,
-                    elapsedRealtime, isIdle)) {
+                    elapsedRealtime, bucket)) {
                 mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
-                        userId, isIdle ? 1 : 0, packageName));
+                        userId, bucket, packageName));
             }
         }
     }
@@ -461,11 +464,13 @@
         if (DEBUG) Slog.i(TAG, "DeviceIdleMode changed to " + deviceIdle);
         boolean paroled = false;
         synchronized (mAppIdleLock) {
-            final long timeSinceLastParole = mInjector.currentTimeMillis() - mLastAppIdleParoledTime;
+            final long timeSinceLastParole =
+                    mInjector.currentTimeMillis() - mLastAppIdleParoledTime;
             if (!deviceIdle
                     && timeSinceLastParole >= mAppIdleParoleIntervalMillis) {
                 if (DEBUG) {
-                    Slog.i(TAG, "Bringing idle apps out of inactive state due to deviceIdleMode=false");
+                    Slog.i(TAG,
+                            "Bringing idle apps out of inactive state due to deviceIdleMode=false");
                 }
                 paroled = true;
             } else if (deviceIdle) {
@@ -491,7 +496,8 @@
                     || event.mEventType == UsageEvents.Event.USER_INTERACTION)) {
                 mAppIdleHistory.reportUsage(event.mPackage, userId, elapsedRealtime);
                 if (previouslyIdle) {
-                    maybeInformListeners(event.mPackage, userId, elapsedRealtime, false);
+                    maybeInformListeners(event.mPackage, userId, elapsedRealtime,
+                            AppStandby.STANDBY_BUCKET_ACTIVE);
                     notifyBatteryStats(event.mPackage, userId, false);
                 }
             }
@@ -729,7 +735,8 @@
 
     void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
             String reason, long elapsedRealtime) {
-        mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket, reason);
+        mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket,
+                reason);
     }
 
     private boolean isActiveDeviceAdmin(String packageName, int userId) {
@@ -786,9 +793,10 @@
         return packageName != null && packageName.equals(activeScorer);
     }
 
-    void informListeners(String packageName, int userId, boolean isIdle) {
+    void informListeners(String packageName, int userId, int bucket) {
+        final boolean idle = bucket >= AppStandby.STANDBY_BUCKET_RARE;
         for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
-            listener.onAppIdleStateChanged(packageName, userId, isIdle);
+            listener.onAppIdleStateChanged(packageName, userId, idle, bucket);
         }
     }
 
@@ -1037,7 +1045,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_INFORM_LISTENERS:
-                    informListeners((String) msg.obj, msg.arg1, msg.arg2 == 1);
+                    informListeners((String) msg.obj, msg.arg1, msg.arg2);
                     break;
 
                 case MSG_FORCE_IDLE_STATE:
@@ -1187,7 +1195,8 @@
                 mAppStandbyScreenThresholds = parseLongArray(screenThresholdsValue,
                         SCREEN_TIME_THRESHOLDS);
 
-                String elapsedThresholdsValue = mParser.getString(KEY_ELAPSED_TIME_THRESHOLDS, null);
+                String elapsedThresholdsValue = mParser.getString(KEY_ELAPSED_TIME_THRESHOLDS,
+                        null);
                 mAppStandbyElapsedThresholds = parseLongArray(elapsedThresholdsValue,
                         ELAPSED_TIME_THRESHOLDS);
             }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 061e55a..0030ab6 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1650,6 +1650,26 @@
     public static final String KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL =
             "identify_high_definition_calls_in_call_log_bool";
 
+    /**
+     * Flag specifying whether to use the {@link ServiceState} roaming status, which can be
+     * affected by other carrier configs (e.g.
+     * {@link #KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY}), when setting the SPN display.
+     * <p>
+     * If {@code true}, the SPN display uses {@link ServiceState#getRoaming}.
+     * If {@code false} the SPN display checks if the current MCC/MNC is different from the
+     * SIM card's MCC/MNC.
+     *
+     * @see KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY
+     * @see KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY
+     * @see KEY_NON_ROAMING_OPERATOR_STRING_ARRAY
+     * @see KEY_ROAMING_OPERATOR_STRING_ARRAY
+     * @see KEY_FORCE_HOME_NETWORK_BOOL
+     *
+     * @hide
+     */
+    public static final String KEY_SPN_DISPLAY_RULE_USE_ROAMING_FROM_SERVICE_STATE_BOOL =
+            "spn_display_rule_use_roaming_from_service_state_bool";
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -1928,6 +1948,7 @@
         sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false);
         sDefaults.putStringArray(KEY_FEATURE_ACCESS_CODES_STRING_ARRAY, null);
         sDefaults.putBoolean(KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL, false);
+        sDefaults.putBoolean(KEY_SPN_DISPLAY_RULE_USE_ROAMING_FROM_SERVICE_STATE_BOOL, false);
     }
 
     /**
diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index afff6d5..9ccfa94 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -204,16 +204,6 @@
     public static final int LISTEN_VOLTE_STATE                              = 0x00004000;
 
     /**
-     * Listen for OEM hook raw event
-     *
-     * @see #onOemHookRawEvent
-     * @hide
-     * @deprecated OEM needs a vendor-extension hal and their apps should use that instead
-     */
-    @Deprecated
-    public static final int LISTEN_OEM_HOOK_RAW_EVENT                       = 0x00008000;
-
-    /**
      * Listen for carrier network changes indicated by a carrier app.
      *
      * @see #onCarrierNetworkRequest
@@ -359,9 +349,6 @@
                     case LISTEN_DATA_ACTIVATION_STATE:
                         PhoneStateListener.this.onDataActivationStateChanged((int)msg.obj);
                         break;
-                    case LISTEN_OEM_HOOK_RAW_EVENT:
-                        PhoneStateListener.this.onOemHookRawEvent((byte[])msg.obj);
-                        break;
                     case LISTEN_CARRIER_NETWORK_CHANGE:
                         PhoneStateListener.this.onCarrierNetworkChange((boolean)msg.obj);
                         break;
@@ -556,16 +543,6 @@
     }
 
     /**
-     * Callback invoked when OEM hook raw event is received. Requires
-     * the READ_PRIVILEGED_PHONE_STATE permission.
-     * @param rawData is the byte array of the OEM hook raw data.
-     * @hide
-     */
-    public void onOemHookRawEvent(byte[] rawData) {
-        // default implementation empty
-    }
-
-    /**
      * Callback invoked when telephony has received notice from a carrier
      * app that a network action that could result in connectivity loss
      * has been requested by an app using
@@ -677,10 +654,6 @@
             send(LISTEN_DATA_ACTIVATION_STATE, 0, 0, activationState);
         }
 
-        public void onOemHookRawEvent(byte[] rawData) {
-            send(LISTEN_OEM_HOOK_RAW_EVENT, 0, 0, rawData);
-        }
-
         public void onCarrierNetworkChange(boolean active) {
             send(LISTEN_CARRIER_NETWORK_CHANGE, 0, 0, active);
         }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 42c3de5..4ffb3c3 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -5709,29 +5709,6 @@
         return retVal;
     }
 
-    /**
-     * Returns the result and response from RIL for oem request
-     *
-     * @param oemReq the data is sent to ril.
-     * @param oemResp the respose data from RIL.
-     * @return negative value request was not handled or get error
-     *         0 request was handled succesfully, but no response data
-     *         positive value success, data length of response
-     * @hide
-     * @deprecated OEM needs a vendor-extension hal and their apps should use that instead
-     */
-    @Deprecated
-    public int invokeOemRilRequestRaw(byte[] oemReq, byte[] oemResp) {
-        try {
-            ITelephony telephony = getITelephony();
-            if (telephony != null)
-                return telephony.invokeOemRilRequestRaw(oemReq, oemResp);
-        } catch (RemoteException ex) {
-        } catch (NullPointerException ex) {
-        }
-        return -1;
-    }
-
     /** @hide */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
diff --git a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
index e9c5461..ac16139 100644
--- a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -45,7 +45,6 @@
     void onVoLteServiceStateChanged(in VoLteServiceState lteState);
     void onVoiceActivationStateChanged(int activationState);
     void onDataActivationStateChanged(int activationState);
-    void onOemHookRawEvent(in byte[] rawData);
     void onCarrierNetworkChange(in boolean active);
 }
 
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 2ac11b5..3cc9bde 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1000,17 +1000,6 @@
             in List<String> cdmaNonRoamingList);
 
     /**
-     * Returns the result and response from RIL for oem request
-     *
-     * @param oemReq the data is sent to ril.
-     * @param oemResp the respose data from RIL.
-     * @return negative value request was not handled or get error
-     *         0 request was handled succesfully, but no response data
-     *         positive value success, data length of response
-     */
-    int invokeOemRilRequestRaw(in byte[] oemReq, out byte[] oemResp);
-
-    /**
      * Check if any mobile Radios need to be shutdown.
      *
      * @return true is any mobile radio needs to be shutdown
diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 2c2206c..75d8f3f 100644
--- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -67,7 +67,6 @@
     void notifyVoLteServiceStateChanged(in VoLteServiceState lteState);
     void notifySimActivationStateChangedForPhoneId(in int phoneId, in int subId,
             int activationState, int activationType);
-    void notifyOemHookRawEventForSubscriber(in int subId, in byte[] rawData);
     void notifySubscriptionInfoChanged();
     void notifyCarrierNetworkChange(in boolean active);
 }
diff --git a/tests/net/java/android/net/MacAddressTest.java b/tests/net/java/android/net/MacAddressTest.java
index 3fa9c3a..fcbb9da 100644
--- a/tests/net/java/android/net/MacAddressTest.java
+++ b/tests/net/java/android/net/MacAddressTest.java
@@ -19,12 +19,14 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
 
 import android.net.MacAddress.MacAddressType;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
 import java.util.Arrays;
+import java.util.Random;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -64,10 +66,139 @@
             String msg = String.format("expected type of %s to be %s, but got %s",
                     Arrays.toString(t.addr), t.expected, got);
             assertEquals(msg, t.expected, got);
+
+            if (got != null) {
+                assertEquals(got, new MacAddress(t.addr).addressType());
+            }
         }
     }
 
-    static byte[] toByteArray(int[] in) {
+    @Test
+    public void testIsMulticastAddress() {
+        MacAddress[] multicastAddresses = {
+            MacAddress.BROADCAST_ADDRESS,
+            new MacAddress("07:00:d3:56:8a:c4"),
+            new MacAddress("33:33:aa:bb:cc:dd"),
+        };
+        MacAddress[] unicastAddresses = {
+            MacAddress.ALL_ZEROS_ADDRESS,
+            new MacAddress("00:01:44:55:66:77"),
+            new MacAddress("08:00:22:33:44:55"),
+            new MacAddress("06:00:00:00:00:00"),
+        };
+
+        for (MacAddress mac : multicastAddresses) {
+            String msg = mac.toString() + " expected to be a multicast address";
+            assertTrue(msg, mac.isMulticastAddress());
+        }
+        for (MacAddress mac : unicastAddresses) {
+            String msg = mac.toString() + " expected not to be a multicast address";
+            assertFalse(msg, mac.isMulticastAddress());
+        }
+    }
+
+    @Test
+    public void testIsLocallyAssignedAddress() {
+        MacAddress[] localAddresses = {
+            new MacAddress("06:00:00:00:00:00"),
+            new MacAddress("07:00:d3:56:8a:c4"),
+            new MacAddress("33:33:aa:bb:cc:dd"),
+        };
+        MacAddress[] universalAddresses = {
+            new MacAddress("00:01:44:55:66:77"),
+            new MacAddress("08:00:22:33:44:55"),
+        };
+
+        for (MacAddress mac : localAddresses) {
+            String msg = mac.toString() + " expected to be a locally assigned address";
+            assertTrue(msg, mac.isLocallyAssigned());
+        }
+        for (MacAddress mac : universalAddresses) {
+            String msg = mac.toString() + " expected not to be globally unique address";
+            assertFalse(msg, mac.isLocallyAssigned());
+        }
+    }
+
+    @Test
+    public void testMacAddressConversions() {
+        final int iterations = 10000;
+        for (int i = 0; i < iterations; i++) {
+            MacAddress mac = MacAddress.getRandomAddress();
+
+            String stringRepr = mac.toString();
+            byte[] bytesRepr = mac.toByteArray();
+
+            assertEquals(mac, new MacAddress(stringRepr));
+            assertEquals(mac, new MacAddress(bytesRepr));
+        }
+    }
+
+    @Test
+    public void testMacAddressRandomGeneration() {
+        final int iterations = 1000;
+        final String expectedAndroidOui = "da:a1:19";
+        for (int i = 0; i < iterations; i++) {
+            MacAddress mac = MacAddress.getRandomAddress();
+            String stringRepr = mac.toString();
+
+            assertTrue(stringRepr + " expected to be a locally assigned address",
+                    mac.isLocallyAssigned());
+            assertTrue(stringRepr + " expected to begin with " + expectedAndroidOui,
+                    stringRepr.startsWith(expectedAndroidOui));
+        }
+
+        final Random r = new Random();
+        final String anotherOui = "24:5f:78";
+        final String expectedLocalOui = "26:5f:78";
+        final MacAddress base = new MacAddress(anotherOui + ":0:0:0");
+        for (int i = 0; i < iterations; i++) {
+            MacAddress mac = MacAddress.getRandomAddress(base, r);
+            String stringRepr = mac.toString();
+
+            assertTrue(stringRepr + " expected to be a locally assigned address",
+                    mac.isLocallyAssigned());
+            assertTrue(stringRepr + " expected to begin with " + expectedLocalOui,
+                    stringRepr.startsWith(expectedLocalOui));
+        }
+    }
+
+    @Test
+    public void testConstructorInputValidation() {
+        String[] invalidStringAddresses = {
+            null,
+            "",
+            "abcd",
+            "1:2:3:4:5",
+            "1:2:3:4:5:6:7",
+            "10000:2:3:4:5:6",
+        };
+
+        for (String s : invalidStringAddresses) {
+            try {
+                MacAddress mac = new MacAddress(s);
+                fail("new MacAddress(" + s + ") should have failed, but returned " + mac);
+            } catch (IllegalArgumentException excepted) {
+            }
+        }
+
+        byte[][] invalidBytesAddresses = {
+            null,
+            {},
+            {1,2,3,4,5},
+            {1,2,3,4,5,6,7},
+        };
+
+        for (byte[] b : invalidBytesAddresses) {
+            try {
+                MacAddress mac = new MacAddress(b);
+                fail("new MacAddress(" + Arrays.toString(b)
+                        + ") should have failed, but returned " + mac);
+            } catch (IllegalArgumentException excepted) {
+            }
+        }
+    }
+
+    static byte[] toByteArray(int... in) {
         byte[] out = new byte[in.length];
         for (int i = 0; i < in.length; i++) {
             out[i] = (byte) in[i];