Merge "Add stable AIDL parcelables for LinkProperties"
diff --git a/api/current.txt b/api/current.txt
index 7c7429f..aaacd68 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -41302,10 +41302,10 @@
     ctor public CallRedirectionService();
     method public final void cancelCall();
     method public final android.os.IBinder onBind(android.content.Intent);
-    method public abstract void onPlaceCall(android.net.Uri, android.telecom.PhoneAccountHandle);
+    method public abstract void onPlaceCall(android.net.Uri, android.telecom.PhoneAccountHandle, boolean);
     method public final boolean onUnbind(android.content.Intent);
     method public final void placeCallUnmodified();
-    method public final void redirectCall(android.net.Uri, android.telecom.PhoneAccountHandle);
+    method public final void redirectCall(android.net.Uri, android.telecom.PhoneAccountHandle, boolean);
     field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.CallRedirectionService";
   }
 
diff --git a/api/system-current.txt b/api/system-current.txt
index 7daab866..1bf6cf9 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5556,9 +5556,9 @@
   public class SubscriptionManager {
     method public java.util.List<android.telephony.SubscriptionInfo> getAvailableSubscriptionInfoList();
     method public void requestEmbeddedSubscriptionInfoListRefresh();
+    method public void requestEmbeddedSubscriptionInfoListRefresh(int);
     method public void setDefaultDataSubId(int);
     method public void setDefaultSmsSubId(int);
-    method public void requestEmbeddedSubscriptionInfoListRefresh(int);
     field public static final android.net.Uri ADVANCED_CALLING_ENABLED_CONTENT_URI;
     field public static final int PROFILE_CLASS_DEFAULT = -1; // 0xffffffff
     field public static final int PROFILE_CLASS_OPERATIONAL = 2; // 0x2
@@ -6116,7 +6116,6 @@
     method public void callSessionInviteParticipantsRequestDelivered();
     method public void callSessionInviteParticipantsRequestFailed(android.telephony.ims.ImsReasonInfo);
     method public void callSessionMayHandover(int, int);
-    method public void callSessionRttAudioIndicatorChanged(android.telephony.ims.ImsStreamMediaProfile);
     method public void callSessionMergeComplete(android.telephony.ims.stub.ImsCallSessionImplBase);
     method public void callSessionMergeFailed(android.telephony.ims.ImsReasonInfo);
     method public void callSessionMergeStarted(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile);
@@ -6127,6 +6126,7 @@
     method public void callSessionResumeFailed(android.telephony.ims.ImsReasonInfo);
     method public void callSessionResumeReceived(android.telephony.ims.ImsCallProfile);
     method public void callSessionResumed(android.telephony.ims.ImsCallProfile);
+    method public void callSessionRttAudioIndicatorChanged(android.telephony.ims.ImsStreamMediaProfile);
     method public void callSessionRttMessageReceived(java.lang.String);
     method public void callSessionRttModifyRequestReceived(android.telephony.ims.ImsCallProfile);
     method public void callSessionRttModifyResponseReceived(int);
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index e717c1e..2bd813e 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -1568,7 +1568,6 @@
 Landroid/test/AndroidTestCase;->getTestContext()Landroid/content/Context;
 Landroid/test/AndroidTestCase;->setTestContext(Landroid/content/Context;)V
 Landroid/test/InstrumentationTestCase;->runMethod(Ljava/lang/reflect/Method;I)V
-Landroid/test/RepetitiveTest;->numIterations()I
 Landroid/util/Singleton;-><init>()V
 Landroid/util/XmlPullAttributes;-><init>(Lorg/xmlpull/v1/XmlPullParser;)V
 Landroid/util/XmlPullAttributes;->mParser:Lorg/xmlpull/v1/XmlPullParser;
@@ -3644,11 +3643,9 @@
 Lcom/android/internal/telephony/SubscriptionInfoUpdater;->mContext:Landroid/content/Context;
 Lcom/android/internal/telephony/SubscriptionInfoUpdater;->mCurrentlyActiveUserId:I
 Lcom/android/internal/telephony/SubscriptionInfoUpdater;->mIccId:[Ljava/lang/String;
-Lcom/android/internal/telephony/SubscriptionInfoUpdater;->mInsertSimState:[I
 Lcom/android/internal/telephony/SubscriptionInfoUpdater;->mPackageManager:Landroid/content/pm/IPackageManager;
 Lcom/android/internal/telephony/SubscriptionInfoUpdater;->mPhone:[Lcom/android/internal/telephony/Phone;
 Lcom/android/internal/telephony/SubscriptionInfoUpdater;->PROJECT_SIM_NUM:I
-Lcom/android/internal/telephony/SubscriptionInfoUpdater;->updateSubscriptionInfoByIccId()V
 Lcom/android/internal/telephony/TelephonyCapabilities;->supportsAdn(I)Z
 Lcom/android/internal/telephony/TelephonyProperties;->PROPERTY_ICC_OPERATOR_NUMERIC:Ljava/lang/String;
 Lcom/android/internal/telephony/test/InterpreterEx;-><init>(Ljava/lang/String;)V
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 617125b3..c2963fd 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -191,6 +191,7 @@
             }
             setMtu(source.mMtu);
             mTcpBufferSizes = source.mTcpBufferSizes;
+            mNat64Prefix = source.mNat64Prefix;
         }
     }
 
diff --git a/core/java/android/net/ipmemorystore/NetworkAttributes.java b/core/java/android/net/ipmemorystore/NetworkAttributes.java
index d7e5b27..b932d21 100644
--- a/core/java/android/net/ipmemorystore/NetworkAttributes.java
+++ b/core/java/android/net/ipmemorystore/NetworkAttributes.java
@@ -28,6 +28,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.StringJoiner;
 
 /**
  * A POD object to represent attributes of a single L2 network entry.
@@ -207,4 +208,52 @@
     public int hashCode() {
         return Objects.hash(assignedV4Address, groupHint, dnsAddresses, mtu);
     }
+
+    /** Pretty print */
+    @Override
+    public String toString() {
+        final StringJoiner resultJoiner = new StringJoiner(" ", "{", "}");
+        final ArrayList<String> nullFields = new ArrayList<>();
+
+        if (null != assignedV4Address) {
+            resultJoiner.add("assignedV4Addr :");
+            resultJoiner.add(assignedV4Address.toString());
+        } else {
+            nullFields.add("assignedV4Addr");
+        }
+
+        if (null != groupHint) {
+            resultJoiner.add("groupHint :");
+            resultJoiner.add(groupHint);
+        } else {
+            nullFields.add("groupHint");
+        }
+
+        if (null != dnsAddresses) {
+            resultJoiner.add("dnsAddr : [");
+            for (final InetAddress addr : dnsAddresses) {
+                resultJoiner.add(addr.getHostAddress());
+            }
+            resultJoiner.add("]");
+        } else {
+            nullFields.add("dnsAddr");
+        }
+
+        if (null != mtu) {
+            resultJoiner.add("mtu :");
+            resultJoiner.add(mtu.toString());
+        } else {
+            nullFields.add("mtu");
+        }
+
+        if (!nullFields.isEmpty()) {
+            resultJoiner.add("; Null fields : [");
+            for (final String field : nullFields) {
+                resultJoiner.add(field);
+            }
+            resultJoiner.add("]");
+        }
+
+        return resultJoiner.toString();
+    }
 }
diff --git a/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java b/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
index 0cb37e9..d040dcc 100644
--- a/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
+++ b/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
@@ -128,4 +128,19 @@
     public int hashCode() {
         return Objects.hash(l2Key1, l2Key2, confidence);
     }
+
+    @Override
+    /** Pretty print */
+    public String toString() {
+        switch (getNetworkSameness()) {
+            case NETWORK_SAME:
+                return "\"" + l2Key1 + "\" same L3 network as \"" + l2Key2 + "\"";
+            case NETWORK_DIFFERENT:
+                return "\"" + l2Key1 + "\" different L3 network from \"" + l2Key2 + "\"";
+            case NETWORK_NEVER_CONNECTED:
+                return "\"" + l2Key1 + "\" can't be tested against \"" + l2Key2 + "\"";
+            default:
+                return "Buggy sameness value ? \"" + l2Key1 + "\", \"" + l2Key2 + "\"";
+        }
+    }
 }
diff --git a/core/java/android/net/ipmemorystore/Status.java b/core/java/android/net/ipmemorystore/Status.java
index 5b016ec..95e5042 100644
--- a/core/java/android/net/ipmemorystore/Status.java
+++ b/core/java/android/net/ipmemorystore/Status.java
@@ -26,6 +26,8 @@
 public class Status {
     public static final int SUCCESS = 0;
 
+    public static final int ERROR_DATABASE_CANNOT_BE_OPENED = -1;
+
     public final int resultCode;
 
     public Status(final int resultCode) {
@@ -47,4 +49,14 @@
     public boolean isSuccess() {
         return SUCCESS == resultCode;
     }
+
+    /** Pretty print */
+    @Override
+    public String toString() {
+        switch (resultCode) {
+            case SUCCESS: return "SUCCESS";
+            case ERROR_DATABASE_CANNOT_BE_OPENED: return "DATABASE CANNOT BE OPENED";
+            default: return "Unknown value ?!";
+        }
+    }
 }
diff --git a/core/java/android/net/ipmemorystore/Utils.java b/core/java/android/net/ipmemorystore/Utils.java
new file mode 100644
index 0000000..73d8c83
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/Utils.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipmemorystore;
+
+import android.annotation.NonNull;
+
+/** {@hide} */
+public class Utils {
+    /** Pretty print */
+    public static String blobToString(final Blob blob) {
+        final StringBuilder sb = new StringBuilder("Blob : [");
+        if (blob.data.length <= 24) {
+            appendByteArray(sb, blob.data, 0, blob.data.length);
+        } else {
+            appendByteArray(sb, blob.data, 0, 16);
+            sb.append("...");
+            appendByteArray(sb, blob.data, blob.data.length - 8, blob.data.length);
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+
+    // Adds the hex representation of the array between the specified indices (inclusive, exclusive)
+    private static void appendByteArray(@NonNull final StringBuilder sb, @NonNull final byte[] ar,
+            final int from, final int to) {
+        for (int i = from; i < to; ++i) {
+            sb.append(String.format("%02X", ar[i]));
+        }
+    }
+}
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index b15a4d3..cbb3909 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -114,7 +114,6 @@
         }
     }
 
-    // TODO(b/111441001) Connect up with BugreportListener methods.
     private final class DumpstateListener extends IDumpstateListener.Stub
             implements DeathRecipient {
         private final BugreportListener mListener;
@@ -130,35 +129,35 @@
 
         @Override
         public void onProgress(int progress) throws RemoteException {
-            // TODO(b/111441001): implement
+            mListener.onProgress(progress);
         }
 
         @Override
         public void onError(int errorCode) throws RemoteException {
-            // TODO(b/111441001): implement
+            mListener.onError(errorCode);
         }
 
         @Override
         public void onFinished(long durationMs, String title, String description)
                 throws RemoteException {
-            // TODO(b/111441001): implement
+            mListener.onFinished(durationMs, title, description);
         }
 
         // Old methods; should go away
         @Override
         public void onProgressUpdated(int progress) throws RemoteException {
-            // TODO(b/111441001): implement
+            // TODO(b/111441001): remove from interface
         }
 
         @Override
         public void onMaxProgressUpdated(int maxProgress) throws RemoteException {
-            // TODO(b/111441001): implement
+            // TODO(b/111441001): remove from interface
         }
 
         @Override
         public void onSectionComplete(String title, int status, int size, int durationMs)
                 throws RemoteException {
-            // TODO(b/111441001): implement
+            // TODO(b/111441001): remove from interface
         }
     }
 }
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index fdd7488..8ced722 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -356,16 +356,6 @@
     void removeVpnUidRanges(int netId, in UidRange[] ranges);
 
     /**
-     * Start the clatd (464xlat) service on the given interface.
-     */
-    void startClatd(String interfaceName);
-
-    /**
-     * Stop the clatd (464xlat) service on the given interface.
-     */
-    void stopClatd(String interfaceName);
-
-    /**
      * Start listening for mobile activity state changes.
      */
     void registerNetworkActivityListener(INetworkActivityListener listener);
diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp
index 49a24a3..b2d3651 100644
--- a/core/jni/android_app_NativeActivity.cpp
+++ b/core/jni/android_app_NativeActivity.cpp
@@ -281,15 +281,18 @@
     std::unique_ptr<NativeCode> code;
     bool needs_native_bridge = false;
 
+    char* nativeloader_error_msg = nullptr;
     void* handle = OpenNativeLibrary(env,
                                      sdkVersion,
                                      pathStr.c_str(),
                                      classLoader,
                                      libraryPath,
                                      &needs_native_bridge,
-                                     &g_error_msg);
+                                     &nativeloader_error_msg);
 
     if (handle == nullptr) {
+        g_error_msg = nativeloader_error_msg;
+        NativeLoaderFreeErrorMessage(nativeloader_error_msg);
         ALOGW("NativeActivity LoadNativeLibrary(\"%s\") failed: %s",
               pathStr.c_str(),
               g_error_msg.c_str());
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 6ce8148..035ee10 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -39,9 +39,6 @@
     name: "privapp-permissions-platform.xml",
     sub_dir: "permissions",
     src: "privapp-permissions-platform.xml",
-    required: [
-        "privapp_whitelist_com.android.settings.intelligence",
-    ],
 }
 
 prebuilt_etc {
@@ -86,6 +83,7 @@
 
 prebuilt_etc {
     name: "privapp_whitelist_com.android.settings.intelligence",
+    product_specific: true,
     sub_dir: "permissions",
     src: "com.android.settings.intelligence.xml",
     filename_from_src: true,
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index afb9781..2d7471d 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -1953,8 +1953,7 @@
 
         @Override
         public void onProgress(int progress) throws RemoteException {
-            // TODO(b/111441001): change max argument?
-            updateProgressInfo(progress, CAPPED_MAX);
+            updateProgressInfo(progress, 100 /* progress is already a percentage; so max = 100 */);
         }
 
         @Override
diff --git a/packages/services/PacProcessor/Android.mk b/packages/services/PacProcessor/Android.mk
index 295b3d8b..be9ba43 100644
--- a/packages/services/PacProcessor/Android.mk
+++ b/packages/services/PacProcessor/Android.mk
@@ -26,6 +26,6 @@
 LOCAL_PRIVATE_PLATFORM_APIS := true
 LOCAL_CERTIFICATE := platform
 
-LOCAL_JNI_SHARED_LIBRARIES := libjni_pacprocessor libpac
+LOCAL_JNI_SHARED_LIBRARIES := libjni_pacprocessor
 
 include $(BUILD_PACKAGE)
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index d0666b9..d6f3e2b 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -4879,7 +4879,7 @@
         final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
         final NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),
                 new Network(reserveNetId()), new NetworkInfo(networkInfo), lp, nc, currentScore,
-                mContext, mTrackerHandler, new NetworkMisc(networkMisc), this);
+                mContext, mTrackerHandler, new NetworkMisc(networkMisc), this, mNetd, mNMS);
         // Make sure the network capabilities reflect what the agent info says.
         nai.networkCapabilities = mixInCapabilities(nai, nc);
         final String extraInfo = networkInfo.getExtraInfo();
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 00b1320..600a6ae 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -17,13 +17,11 @@
 package com.android.server;
 
 import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
-import static android.Manifest.permission.DUMP;
 import static android.Manifest.permission.NETWORK_SETTINGS;
 import static android.Manifest.permission.NETWORK_STACK;
 import static android.Manifest.permission.SHUTDOWN;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
-import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_NONE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NONE;
@@ -40,6 +38,7 @@
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
 import static android.net.TrafficStats.UID_TETHERING;
+
 import static com.android.server.NetworkManagementService.NetdResponseCode.ClatdStatusResult;
 import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceGetCfgResult;
 import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceListResult;
@@ -53,11 +52,9 @@
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.INetd;
-import android.net.TetherStatsParcel;
 import android.net.INetworkManagementEventObserver;
 import android.net.ITetheringStatsProvider;
 import android.net.InterfaceConfiguration;
@@ -69,18 +66,15 @@
 import android.net.NetworkStats;
 import android.net.NetworkUtils;
 import android.net.RouteInfo;
+import android.net.TetherStatsParcel;
 import android.net.UidRange;
-import android.net.UidRangeParcel;
 import android.net.util.NetdService;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiConfiguration.KeyMgmt;
 import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.INetworkActivityListener;
 import android.os.INetworkManagementService;
-import android.os.PersistableBundle;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteCallbackList;
@@ -91,12 +85,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.Trace;
-import android.provider.Settings;
 import android.telephony.DataConnectionRealTimeInfo;
-import android.telephony.PhoneStateListener;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
@@ -109,13 +98,11 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.HexDump;
 import com.android.internal.util.Preconditions;
-import com.android.server.NativeDaemonConnector.Command;
-import com.android.server.NativeDaemonConnector.SensitiveArg;
+
 import com.google.android.collect.Maps;
 
 import java.io.BufferedReader;
 import java.io.DataInputStream;
-import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -123,15 +110,11 @@
 import java.io.PrintWriter;
 import java.net.InetAddress;
 import java.net.InterfaceAddress;
-import java.net.NetworkInterface;
-import java.net.SocketException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.StringTokenizer;
 import java.util.concurrent.CountDownLatch;
 
 /**
@@ -2147,28 +2130,6 @@
     }
 
     @Override
-    public void startClatd(String interfaceName) throws IllegalStateException {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-
-        try {
-            mNetdService.clatdStart(interfaceName);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    @Override
-    public void stopClatd(String interfaceName) throws IllegalStateException {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-
-        try {
-            mNetdService.clatdStop(interfaceName);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    @Override
     public void registerNetworkActivityListener(INetworkActivityListener listener) {
         mNetworkActivityListeners.register(listener);
     }
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index 6596d27..9d9b1cf 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -16,8 +16,9 @@
 
 package com.android.server.connectivity;
 
-import android.net.InterfaceConfiguration;
 import android.net.ConnectivityManager;
+import android.net.INetd;
+import android.net.InterfaceConfiguration;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.NetworkInfo;
@@ -59,6 +60,7 @@
         NetworkInfo.State.SUSPENDED,
     };
 
+    private final INetd mNetd;
     private final INetworkManagementService mNMService;
 
     // The network we're running on, and its type.
@@ -76,7 +78,8 @@
     private String mIface;
     private State mState = State.IDLE;
 
-    public Nat464Xlat(INetworkManagementService nmService, NetworkAgentInfo nai) {
+    public Nat464Xlat(NetworkAgentInfo nai, INetd netd, INetworkManagementService nmService) {
+        mNetd = netd;
         mNMService = nmService;
         mNetwork = nai;
     }
@@ -140,7 +143,7 @@
             return;
         }
         try {
-            mNMService.startClatd(baseIface);
+            mNetd.clatdStart(baseIface);
         } catch(RemoteException|IllegalStateException e) {
             Slog.e(TAG, "Error starting clatd on " + baseIface, e);
         }
@@ -162,7 +165,7 @@
      */
     private void enterStoppingState() {
         try {
-            mNMService.stopClatd(mBaseIface);
+            mNetd.clatdStop(mBaseIface);
         } catch(RemoteException|IllegalStateException e) {
             Slog.e(TAG, "Error stopping clatd on " + mBaseIface, e);
         }
@@ -204,7 +207,7 @@
             Slog.e(TAG, "startClat: Can't start clat on null interface");
             return;
         }
-        // TODO: should we only do this if mNMService.startClatd() succeeds?
+        // TODO: should we only do this if mNetd.clatdStart() succeeds?
         Slog.i(TAG, "Starting clatd on " + baseIface);
         enterStartingState(baseIface);
     }
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 54c89aa..9ea73fb 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -17,6 +17,7 @@
 package com.android.server.connectivity;
 
 import android.content.Context;
+import android.net.INetd;
 import android.net.INetworkMonitor;
 import android.net.LinkProperties;
 import android.net.Network;
@@ -239,12 +240,15 @@
     private static final String TAG = ConnectivityService.class.getSimpleName();
     private static final boolean VDBG = false;
     private final ConnectivityService mConnService;
+    private final INetd mNetd;
+    private final INetworkManagementService mNMS;
     private final Context mContext;
     private final Handler mHandler;
 
     public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info,
             LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler,
-            NetworkMisc misc, ConnectivityService connService) {
+            NetworkMisc misc, ConnectivityService connService, INetd netd,
+            INetworkManagementService nms) {
         this.messenger = messenger;
         asyncChannel = ac;
         network = net;
@@ -253,6 +257,8 @@
         networkCapabilities = nc;
         currentScore = score;
         mConnService = connService;
+        mNetd = netd;
+        mNMS = nms;
         mContext = context;
         mHandler = handler;
         networkMisc = misc;
@@ -587,18 +593,18 @@
 
     public void updateClat(INetworkManagementService netd) {
         if (Nat464Xlat.requiresClat(this)) {
-            maybeStartClat(netd);
+            maybeStartClat();
         } else {
             maybeStopClat();
         }
     }
 
     /** Ensure clat has started for this network. */
-    public void maybeStartClat(INetworkManagementService netd) {
+    public void maybeStartClat() {
         if (clatd != null && clatd.isStarted()) {
             return;
         }
-        clatd = new Nat464Xlat(netd, this);
+        clatd = new Nat464Xlat(this, mNetd, mNMS);
         clatd.start();
     }
 
diff --git a/services/core/java/com/android/server/connectivity/PacManager.java b/services/core/java/com/android/server/connectivity/PacManager.java
index 3ea9810..9789688 100644
--- a/services/core/java/com/android/server/connectivity/PacManager.java
+++ b/services/core/java/com/android/server/connectivity/PacManager.java
@@ -282,6 +282,7 @@
     private void setCurrentProxyScript(String script) {
         if (mProxyService == null) {
             Log.e(TAG, "setCurrentProxyScript: no proxy service");
+            return;
         }
         try {
             mProxyService.setPacFile(script);
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
new file mode 100644
index 0000000..eaab650
--- /dev/null
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.ipmemorystore;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+/**
+ * Encapsulating class for using the SQLite database backing the memory store.
+ *
+ * This class groups together the contracts and the SQLite helper used to
+ * use the database.
+ *
+ * @hide
+ */
+public class IpMemoryStoreDatabase {
+    /**
+     * Contract class for the Network Attributes table.
+     */
+    public static class NetworkAttributesContract {
+        public static final String TABLENAME = "NetworkAttributes";
+
+        public static final String COLNAME_L2KEY = "l2Key";
+        public static final String COLTYPE_L2KEY = "TEXT NOT NULL";
+
+        public static final String COLNAME_EXPIRYDATE = "expiryDate";
+        // Milliseconds since the Epoch, in true Java style
+        public static final String COLTYPE_EXPIRYDATE = "BIGINT";
+
+        public static final String COLNAME_ASSIGNEDV4ADDRESS = "assignedV4Address";
+        public static final String COLTYPE_ASSIGNEDV4ADDRESS = "INTEGER";
+
+        // Please note that the group hint is only a *hint*, hence its name. The client can offer
+        // this information to nudge the grouping in the decision it thinks is right, but it can't
+        // decide for the memory store what is the same L3 network.
+        public static final String COLNAME_GROUPHINT = "groupHint";
+        public static final String COLTYPE_GROUPHINT = "TEXT";
+
+        public static final String COLNAME_DNSADDRESSES = "dnsAddresses";
+        // Stored in marshalled form as is
+        public static final String COLTYPE_DNSADDRESSES = "BLOB";
+
+        public static final String COLNAME_MTU = "mtu";
+        public static final String COLTYPE_MTU = "INTEGER";
+
+        public static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS "
+                + TABLENAME                 + " ("
+                + COLNAME_L2KEY             + " " + COLTYPE_L2KEY + " PRIMARY KEY NOT NULL, "
+                + COLNAME_EXPIRYDATE        + " " + COLTYPE_EXPIRYDATE        + ", "
+                + COLNAME_ASSIGNEDV4ADDRESS + " " + COLTYPE_ASSIGNEDV4ADDRESS + ", "
+                + COLNAME_GROUPHINT         + " " + COLTYPE_GROUPHINT         + ", "
+                + COLNAME_DNSADDRESSES      + " " + COLTYPE_DNSADDRESSES      + ", "
+                + COLNAME_MTU               + " " + COLTYPE_MTU               + ")";
+        public static final String DROP_TABLE = "DROP TABLE IF EXISTS " + TABLENAME;
+    }
+
+    /**
+     * Contract class for the Private Data table.
+     */
+    public static class PrivateDataContract {
+        public static final String TABLENAME = "PrivateData";
+
+        public static final String COLNAME_L2KEY = "l2Key";
+        public static final String COLTYPE_L2KEY = "TEXT NOT NULL";
+
+        public static final String COLNAME_CLIENT = "client";
+        public static final String COLTYPE_CLIENT = "TEXT NOT NULL";
+
+        public static final String COLNAME_DATANAME = "dataName";
+        public static final String COLTYPE_DATANAME = "TEXT NOT NULL";
+
+        public static final String COLNAME_DATA = "data";
+        public static final String COLTYPE_DATA = "BLOB NOT NULL";
+
+        public static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS "
+                + TABLENAME        + " ("
+                + COLNAME_L2KEY    + " " + COLTYPE_L2KEY    + ", "
+                + COLNAME_CLIENT   + " " + COLTYPE_CLIENT   + ", "
+                + COLNAME_DATANAME + " " + COLTYPE_DATANAME + ", "
+                + COLNAME_DATA     + " " + COLTYPE_DATA     + ", "
+                + "PRIMARY KEY ("
+                + COLNAME_L2KEY    + ", "
+                + COLNAME_CLIENT   + ", "
+                + COLNAME_DATANAME + "))";
+        public static final String DROP_TABLE = "DROP TABLE IF EXISTS " + TABLENAME;
+    }
+
+    // To save memory when the DB is not used, close it after 30s of inactivity. This is
+    // determined manually based on what feels right.
+    private static final long IDLE_CONNECTION_TIMEOUT_MS = 30_000;
+
+    /** The SQLite DB helper */
+    public static class DbHelper extends SQLiteOpenHelper {
+        // Update this whenever changing the schema.
+        private static final int SCHEMA_VERSION = 1;
+        private static final String DATABASE_FILENAME = "IpMemoryStore.db";
+
+        public DbHelper(@NonNull final Context context) {
+            super(context, DATABASE_FILENAME, null, SCHEMA_VERSION);
+            setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS);
+        }
+
+        /** Called when the database is created */
+        public void onCreate(@NonNull final SQLiteDatabase db) {
+            db.execSQL(NetworkAttributesContract.CREATE_TABLE);
+            db.execSQL(PrivateDataContract.CREATE_TABLE);
+        }
+
+        /** Called when the database is upgraded */
+        public void onUpgrade(@NonNull final SQLiteDatabase db, final int oldVersion,
+                final int newVersion) {
+            // No upgrade supported yet.
+            db.execSQL(NetworkAttributesContract.DROP_TABLE);
+            db.execSQL(PrivateDataContract.DROP_TABLE);
+            onCreate(db);
+        }
+
+        /** Called when the database is downgraded */
+        public void onDowngrade(@NonNull final SQLiteDatabase db, final int oldVersion,
+                final int newVersion) {
+            // Downgrades always nuke all data and recreate an empty table.
+            db.execSQL(NetworkAttributesContract.DROP_TABLE);
+            db.execSQL(PrivateDataContract.DROP_TABLE);
+            onCreate(db);
+        }
+    }
+}
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
index c9759bf..55a72190 100644
--- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
@@ -19,6 +19,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
 import android.net.IIpMemoryStore;
 import android.net.ipmemorystore.Blob;
 import android.net.ipmemorystore.IOnBlobRetrievedListener;
@@ -27,6 +29,10 @@
 import android.net.ipmemorystore.IOnSameNetworkResponseListener;
 import android.net.ipmemorystore.IOnStatusListener;
 import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.util.Log;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 /**
  * Implementation for the IP memory store.
@@ -37,10 +43,75 @@
  * @hide
  */
 public class IpMemoryStoreService extends IIpMemoryStore.Stub {
-    final Context mContext;
+    private static final String TAG = IpMemoryStoreService.class.getSimpleName();
+    private static final int MAX_CONCURRENT_THREADS = 4;
 
+    @NonNull
+    final Context mContext;
+    @Nullable
+    final SQLiteDatabase mDb;
+    @NonNull
+    final ExecutorService mExecutor;
+
+    /**
+     * Construct an IpMemoryStoreService object.
+     * This constructor will block on disk access to open the database.
+     * @param context the context to access storage with.
+     */
     public IpMemoryStoreService(@NonNull final Context context) {
+        // Note that constructing the service will access the disk and block
+        // for some time, but it should make no difference to the clients. Because
+        // the interface is one-way, clients fire and forget requests, and the callback
+        // will get called eventually in any case, and the framework will wait for the
+        // service to be created to deliver subsequent requests.
+        // Avoiding this would mean the mDb member can't be final, which means the service would
+        // have to test for nullity, care for failure, and allow for a wait at every single access,
+        // which would make the code a lot more complex and require all methods to possibly block.
         mContext = context;
+        SQLiteDatabase db;
+        final IpMemoryStoreDatabase.DbHelper helper = new IpMemoryStoreDatabase.DbHelper(context);
+        try {
+            db = helper.getWritableDatabase();
+            if (null == db) Log.e(TAG, "Unexpected null return of getWriteableDatabase");
+        } catch (final SQLException e) {
+            Log.e(TAG, "Can't open the Ip Memory Store database", e);
+            db = null;
+        } catch (final Exception e) {
+            Log.wtf(TAG, "Impossible exception Ip Memory Store database", e);
+            db = null;
+        }
+        mDb = db;
+        // The work-stealing thread pool executor will spawn threads as needed up to
+        // the max only when there is no free thread available. This generally behaves
+        // exactly like one would expect it intuitively :
+        // - When work arrives, it will spawn a new thread iff there are no available threads
+        // - When there is no work to do it will shutdown threads after a while (the while
+        //   being equal to 2 seconds (not configurable) when max threads are spun up and
+        //   twice as much for every one less thread)
+        // - When all threads are busy the work is enqueued and waits for any worker
+        //   to become available.
+        // Because the stealing pool is made for very heavily parallel execution of
+        // small tasks that spawn others, it creates a queue per thread that in this
+        // case is overhead. However, the three behaviors above make it a superior
+        // choice to cached or fixedThreadPoolExecutor, neither of which can actually
+        // enqueue a task waiting for a thread to be free. This can probably be solved
+        // with judicious subclassing of ThreadPoolExecutor, but that's a lot of dangerous
+        // complexity for little benefit in this case.
+        mExecutor = Executors.newWorkStealingPool(MAX_CONCURRENT_THREADS);
+    }
+
+    /**
+     * Shutdown the memory store service, cancelling running tasks and dropping queued tasks.
+     *
+     * This is provided to give a way to clean up, and is meant to be available in case of an
+     * emergency shutdown.
+     */
+    public void shutdown() {
+        // By contrast with ExecutorService#shutdown, ExecutorService#shutdownNow tries
+        // to cancel the existing tasks, and does not wait for completion. It does not
+        // guarantee the threads can be terminated in any given amount of time.
+        mExecutor.shutdownNow();
+        if (mDb != null) mDb.close();
     }
 
     /**
@@ -61,7 +132,7 @@
     public void storeNetworkAttributes(@NonNull final String l2Key,
             @NonNull final NetworkAttributesParcelable attributes,
             @Nullable final IOnStatusListener listener) {
-        // TODO : implement this
+        // TODO : implement this.
     }
 
     /**
@@ -79,7 +150,7 @@
     public void storeBlob(@NonNull final String l2Key, @NonNull final String clientId,
             @NonNull final String name, @NonNull final Blob data,
             @Nullable final IOnStatusListener listener) {
-        // TODO : implement this
+        // TODO : implement this.
     }
 
     /**
@@ -99,7 +170,7 @@
     @Override
     public void findL2Key(@NonNull final NetworkAttributesParcelable attributes,
             @NonNull final IOnL2KeyResponseListener listener) {
-        // TODO : implement this
+        // TODO : implement this.
     }
 
     /**
@@ -114,7 +185,7 @@
     @Override
     public void isSameNetwork(@NonNull final String l2Key1, @NonNull final String l2Key2,
             @NonNull final IOnSameNetworkResponseListener listener) {
-        // TODO : implement this
+        // TODO : implement this.
     }
 
     /**
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/RelevanceUtils.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/RelevanceUtils.java
new file mode 100644
index 0000000..aa45400
--- /dev/null
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/RelevanceUtils.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.ipmemorystore;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * A class containing the logic around the relevance value for
+ * IP Memory Store.
+ *
+ * @hide
+ */
+public class RelevanceUtils {
+    /**
+     * The relevance is a decaying value that gets lower and lower until it
+     * reaches 0 after some time passes. It follows an exponential decay law,
+     * dropping slowly at first then faster and faster, because a network is
+     * likely to be visited again if it was visited not long ago, and the longer
+     * it hasn't been visited the more likely it is that it won't be visited
+     * again. For example, a network visited on holiday should stay fresh for
+     * the duration of the holiday and persist for a while, but after the venue
+     * hasn't been visited for a while it should quickly be discarded. What
+     * should accelerate forgetting the network is extended periods without
+     * visits, so that occasional venues get discarded but regular visits keep
+     * the network relevant, even if the visits are infrequent.
+     *
+     * This function must be stable by iteration, meaning that adjusting the same value
+     * for different dates iteratively multiple times should give the same result.
+     * Formally, if f is the decay function that associates a relevance x at a date d1
+     * to the value at ulterior date d3, then for any date d2 between d1 and d3 :
+     * f(x, d3 - d1) = f(f(x, d3 - d2), d2 - d1). Intuitively, this property simply
+     * means it should be the same to compute and store back the value after two months,
+     * or to do it once after one month, store it back, and do it again after another
+     * months has passed.
+     * The pair of the relevance and date define the entire curve, so any pair
+     * of values on the curve will define the same curve. Setting one of them to a
+     * constant, so as not to have to store it, means the other one will always suffice
+     * to describe the curve. For example, only storing the date for a known, constant
+     * value of the relevance is an efficient way of remembering this information (and
+     * to compare relevances together, as f is monotonically decreasing).
+     *
+     *** Choosing the function :
+     * Functions of the kind described above are standard exponential decay functions
+     * like the ones that govern atomic decay where the value at any given date can be
+     * computed uniformly from the value at a previous date and the time elapsed since
+     * that date. It is simple to picture this kind of function as one where after a
+     * given period of time called the half-life, the relevance value will have been
+     * halved. Decay of this kind is expressed in function of the previous value by
+     * functions like
+     * f(x, t) = x * F ^ (t / L)
+     * ...where x is the value, t is the elapsed time, L is the half-life (or more
+     * generally the F-th-life) and F the decay factor (typically 0.5, hence why L is
+     * usually called the half-life). The ^ symbol here is used for exponentiation.
+     * Or, starting at a given M for t = 0 :
+     * f(t) = M * F ^ (t / L)
+     *
+     * Because a line in the store needs to become irrelevant at some point but
+     * this class of functions never go to 0, a minimum cutoff has to be chosen to
+     * represent irrelevance. The simpler way of doing this is to simply add this
+     * minimum cutoff to the computation before and removing it after.
+     * Thus the function becomes :
+     * f(x, t) = ((x + K) * F ^ (t / L)) - K
+     * ...where K is the minimum cutoff, L the half-life, and F the factor between
+     * the original x and x after its half-life. Strictly speaking using the word
+     * "half-life" implies that F = 0.5, but the relation works for any value of F.
+     *
+     * It is easy enough to check that this function satisfies the stability
+     * relation that was given above for any value of F, L and K, which become
+     * parameters that can be defined at will.
+     *
+     * relevance
+     *  1.0 |
+     *      |\
+     *      | \
+     *      |  \            (this graph rendered with L = 75 days and K = 1/40)
+     *  0.75|   ',
+     *      |     \
+     *      |      '.
+     *      |        \.
+     *      |          \
+     *  0.5 |           '\
+     *      |             ''.
+     *      |                ''.
+     *      |                   ''.
+     *  0.25|                      '''..
+     *      |                           '''..
+     *      |                                ''''....
+     *      |                                        '''''..........
+     *    0 +-------------------------------------------------------''''''''''----
+     *      0       50       100      150     200      250     300      350     400 days
+     *
+     *** Choosing the parameters
+     * The maximum M is an arbitrary parameter that simply scales the curve.
+     * The tradeoff for M is pretty simple : if the relevance is going to be an
+     * integer, the bigger M is the more precision there is in the relevance.
+     * However, values of M that are easy for humans to read are preferable to
+     * help debugging, and a suitably low value may be enough to ensure there
+     * won't be integer overflows in intermediate computations.
+     * A value of 1_000_000 probably is plenty for precision, while still in the
+     * low range of what ints can represent.
+     *
+     * F and L are parameters to be chosen arbitrarily and have an impact on how
+     * fast the relevance will be decaying at first, keeping in mind that
+     * the 400 days value and the cap stay the same. In simpler words, F and L
+     * define the steepness of the curve.
+     * To keep things simple (and familiar) F is arbitrarily chosen to be 0.5, and
+     * L is set to 200 days visually to achieve the desired effect. Refer to the
+     * illustration above to get a feel of how that feels.
+     *
+     * Moreover, the memory store works on an assumption that the relevance should
+     * be capped, and that an entry with capped relevance should decay in 400 days.
+     * This is on premises that the networks a device will need to remember the
+     * longest should be networks visited about once a year.
+     * For this reason, the relevance is at the maximum M 400 days before expiry :
+     * f(M, 400 days) = 0
+     * From replacing this with the value of the function, K can then be derived
+     * from the values of M, F and L :
+     * (M + K) * F ^ (t / L) - K = 0
+     * K = M * F ^ (400 days / L) / (1 - F ^ (400 days / L))
+     * Replacing with actual values this gives :
+     * K = 1_000_000 * 0.5 ^ (400 / 200) / (1 - 0.5 ^ (400 / 200))
+     *   = 1_000_000 / 3 ≈ 333_333.3
+     * This ensures the function has the desired profile, the desired value at
+     * cap, and the desired value at expiry.
+     *
+     *** Useful relations
+     * Let's define the expiry time for any given relevance x as the interval of
+     * time such as :
+     * f(x, expiry) = 0
+     * which can be rewritten
+     * ((x + K) * F ^ (expiry / L)) = K
+     * ...giving an expression of the expiry in function of the relevance x as
+     * expiry = L * logF(K / (x + K))
+     * Conversely the relevance x can be expressed in function of the expiry as
+     * x = K / F ^ (expiry / L) - K
+     * These relations are useful in utility functions.
+     *
+     *** Bumping things up
+     * The last issue therefore is to decide how to bump up the relevance. The
+     * simple approach is to simply lift up the curve a little bit by a constant
+     * normalized amount, delaying the time of expiry. For example increasing
+     * the relevance by an amount I gives :
+     * x2 = x1 + I
+     * x2 and x1 correspond to two different expiry times expiry2 and expiry1,
+     * and replacing x1 and x2 in the relation above with their expression in
+     * function of the expiry comes :
+     * K / F ^ (expiry2 / L) - K = K / F ^ (expiry1 / L) - K + I
+     * which resolves to :
+     * expiry2 = L * logF(K / (I + K / F ^ (expiry1 / L)))
+     *
+     * In this implementation, the bump is defined as 1/25th of the cap for
+     * the relevance. This means a network will be remembered for the maximum
+     * period of 400 days if connected 25 times in succession not accounting
+     * for decay. Of course decay actually happens so it will take more than 25
+     * connections for any given network to actually reach the cap, but because
+     * decay is slow at first, it is a good estimate of how fast cap happens.
+     *
+     * Specifically, it gives the following four results :
+     * - A network that a device connects to once hits irrelevance about 32.7 days after
+     *   it was first registered if never connected again.
+     * - A network that a device connects to once a day at a fixed hour will hit the cap
+     *   on the 27th connection.
+     * - A network that a device connects to once a week at a fixed hour will hit the cap
+     *   on the 57th connection.
+     * - A network that a device connects to every day for 7 straight days then never again
+     *   expires 144 days after the last connection.
+     * These metrics tend to match pretty well the requirements.
+     */
+
+    // TODO : make these constants configurable at runtime. Don't forget to build it so that
+    // changes will wipe the database, migrate the values, or otherwise make sure the relevance
+    // values are still meaningful.
+
+    // How long, in milliseconds, is a capped relevance valid for, or in other
+    // words how many milliseconds after its relevance was set to RELEVANCE_CAP does
+    // any given line expire. 400 days.
+    @VisibleForTesting
+    public static final long CAPPED_RELEVANCE_LIFETIME_MS = 400L * 24 * 60 * 60 * 1000;
+
+    // The constant that represents a normalized 1.0 value for the relevance. In other words,
+    // the cap for the relevance. This is referred to as M in the explanation above.
+    @VisibleForTesting
+    public static final int CAPPED_RELEVANCE = 1_000_000;
+
+    // The decay factor. After a half-life, the relevance will have decayed by this value.
+    // This is referred to as F in the explanation above.
+    private static final double DECAY_FACTOR = 0.5;
+
+    // The half-life. After this time, the relevance will have decayed by a factor DECAY_FACTOR.
+    // This is referred to as L in the explanation above.
+    private static final long HALF_LIFE_MS = 200L * 24 * 60 * 60 * 1000;
+
+    // The value of the frame change. This is referred to as K in the explanation above.
+    private static final double IRRELEVANCE_FLOOR =
+            CAPPED_RELEVANCE * powF((double) CAPPED_RELEVANCE_LIFETIME_MS / HALF_LIFE_MS)
+            / (1 - powF((double) CAPPED_RELEVANCE_LIFETIME_MS / HALF_LIFE_MS));
+
+    // How much to bump the relevance by every time a line is written to.
+    @VisibleForTesting
+    public static final int RELEVANCE_BUMP = CAPPED_RELEVANCE / 25;
+
+    // Java doesn't include a function for the logarithm in an arbitrary base, so implement it
+    private static final double LOG_DECAY_FACTOR = Math.log(DECAY_FACTOR);
+    private static double logF(final double value) {
+        return Math.log(value) / LOG_DECAY_FACTOR;
+    }
+
+    // Utility function to get a power of the decay factor, to simplify the code.
+    private static double powF(final double value) {
+        return Math.pow(DECAY_FACTOR, value);
+    }
+
+    /**
+     * Compute the value of the relevance now given an expiry date.
+     *
+     * @param expiry the date at which the column in the database expires.
+     * @return the adjusted value of the relevance for this moment in time.
+     */
+    public static int computeRelevanceForNow(final long expiry) {
+        return computeRelevanceForTargetDate(expiry, System.currentTimeMillis());
+    }
+
+    /**
+     * Compute the value of the relevance at a given date from an expiry date.
+     *
+     * Because relevance decays with time, a relevance in the past corresponds to
+     * a different relevance later.
+     *
+     * Relevance is always a positive value. 0 means not relevant at all.
+     *
+     * See the explanation at the top of this file to get the justification for this
+     * computation.
+     *
+     * @param expiry the date at which the column in the database expires.
+     * @param target the target date to adjust the relevance to.
+     * @return the adjusted value of the relevance for the target moment.
+     */
+    public static int computeRelevanceForTargetDate(final long expiry, final long target) {
+        final long delay = expiry - target;
+        if (delay >= CAPPED_RELEVANCE_LIFETIME_MS) return CAPPED_RELEVANCE;
+        if (delay <= 0) return 0;
+        return (int) (IRRELEVANCE_FLOOR / powF((float) delay / HALF_LIFE_MS) - IRRELEVANCE_FLOOR);
+    }
+
+    /**
+     * Compute the expiry duration adjusted up for a new fresh write.
+     *
+     * Every time data is written to the memory store for a given line, the
+     * relevance is bumped up by a certain amount, which will boost the priority
+     * of this line for computation of group attributes, and delay (possibly
+     * indefinitely, if the line is accessed regularly) forgetting the data stored
+     * in that line.
+     * As opposed to bumpExpiryDate, this function uses a duration from now to expiry.
+     *
+     * See the explanation at the top of this file for a justification of this computation.
+     *
+     * @param oldExpiryDuration the old expiry duration in milliseconds from now.
+     * @return the expiry duration representing a bumped up relevance value.
+     */
+    public static long bumpExpiryDuration(final long oldExpiryDuration) {
+        // L * logF(K / (I + K / F ^ (expiry1 / L))), as documented above
+        final double divisionFactor = powF(((double) oldExpiryDuration) / HALF_LIFE_MS);
+        final double oldRelevance = IRRELEVANCE_FLOOR / divisionFactor;
+        final long newDuration =
+                (long) (HALF_LIFE_MS * logF(IRRELEVANCE_FLOOR / (RELEVANCE_BUMP + oldRelevance)));
+        return Math.min(newDuration, CAPPED_RELEVANCE_LIFETIME_MS);
+    }
+
+    /**
+     * Compute the new expiry date adjusted up for a new fresh write.
+     *
+     * Every time data is written to the memory store for a given line, the
+     * relevance is bumped up by a certain amount, which will boost the priority
+     * of this line for computation of group attributes, and delay (possibly
+     * indefinitely, if the line is accessed regularly) forgetting the data stored
+     * in that line.
+     * As opposed to bumpExpiryDuration, this function takes the old timestamp and returns the
+     * new timestamp.
+     *
+     * {@see bumpExpiryDuration}, and keep in mind that the bump depends on when this is called,
+     * because the relevance decays exponentially, therefore bumping up a high relevance (for a
+     * date far in the future) is less potent than bumping up a low relevance (for a date in
+     * a close future).
+     *
+     * @param oldExpiryDate the old date of expiration.
+     * @return the new expiration date after the relevance bump.
+     */
+    public static long bumpExpiryDate(final long oldExpiryDate) {
+        final long now = System.currentTimeMillis();
+        final long newDuration = bumpExpiryDuration(oldExpiryDate - now);
+        return now + newDuration;
+    }
+}
diff --git a/telecomm/java/android/telecom/CallRedirectionService.java b/telecomm/java/android/telecom/CallRedirectionService.java
index b906d0b..3299117 100644
--- a/telecomm/java/android/telecom/CallRedirectionService.java
+++ b/telecomm/java/android/telecom/CallRedirectionService.java
@@ -16,6 +16,7 @@
 
 package android.telecom;
 
+import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.app.Service;
 import android.content.Intent;
@@ -27,8 +28,8 @@
 import android.os.RemoteException;
 
 import com.android.internal.os.SomeArgs;
-import com.android.internal.telecom.ICallRedirectionService;
 import com.android.internal.telecom.ICallRedirectionAdapter;
+import com.android.internal.telecom.ICallRedirectionService;
 
 /**
  * This service can be implemented to interact between Telecom and its implementor
@@ -62,22 +63,35 @@
 
     /**
      * Telecom calls this method to inform the implemented {@link CallRedirectionService} of
-     * a new outgoing call which is being placed.
+     * a new outgoing call which is being placed. Telecom does not request to redirect emergency
+     * calls and does not request to redirect calls with gateway information.
      *
-     * The implemented {@link CallRedirectionService} can call {@link #placeCallUnmodified()},
-     * {@link #redirectCall(Uri, PhoneAccountHandle)}, and {@link #cancelCall()} only from here.
+     * <p>Telecom will cancel the call if Telecom does not receive a response in 5 seconds from
+     * the implemented {@link CallRedirectionService} set by users.
      *
-     * @param handle the phone number dialed by the user
-     * @param targetPhoneAccount the {@link PhoneAccountHandle} on which the call will be placed.
+     * <p>The implemented {@link CallRedirectionService} can call {@link #placeCallUnmodified()},
+     * {@link #redirectCall(Uri, PhoneAccountHandle, boolean)}, and {@link #cancelCall()} only
+     * from here.
+     *
+     * @param handle the phone number dialed by the user, represented in E.164 format if possible
+     * @param initialPhoneAccount the {@link PhoneAccountHandle} on which the call will be placed.
+     * @param allowInteractiveResponse a boolean to tell if the implemented
+     *                                 {@link CallRedirectionService} should allow interactive
+     *                                 responses with users. Will be {@code false} if, for example
+     *                                 the device is in car mode and the user would not be able to
+     *                                 interact with their device.
      */
-    public abstract void onPlaceCall(Uri handle, PhoneAccountHandle targetPhoneAccount);
+    public abstract void onPlaceCall(@NonNull Uri handle,
+                                     @NonNull PhoneAccountHandle initialPhoneAccount,
+                                     boolean allowInteractiveResponse);
 
     /**
      * The implemented {@link CallRedirectionService} calls this method to response a request
-     * received via {@link #onPlaceCall(Uri, PhoneAccountHandle)} to inform Telecom that no changes
-     * are required to the outgoing call, and that the call should be placed as-is.
+     * received via {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)} to inform Telecom that
+     * no changes are required to the outgoing call, and that the call should be placed as-is.
      *
-     * This can only be called from implemented {@link #onPlaceCall(Uri, PhoneAccountHandle)}.
+     * <p>This can only be called from implemented
+     * {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)}.
      *
      */
     public final void placeCallUnmodified() {
@@ -89,29 +103,39 @@
 
     /**
      * The implemented {@link CallRedirectionService} calls this method to response a request
-     * received via {@link #onPlaceCall(Uri, PhoneAccountHandle)} to inform Telecom that changes
-     * are required to the phone number or/and {@link PhoneAccountHandle} for the outgoing call.
+     * received via {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)} to inform Telecom that
+     * changes are required to the phone number or/and {@link PhoneAccountHandle} for the outgoing
+     * call. Telecom will cancel the call if the implemented {@link CallRedirectionService}
+     * replies Telecom a handle for an emergency number.
      *
-     * This can only be called from implemented {@link #onPlaceCall(Uri, PhoneAccountHandle)}.
+     * <p>This can only be called from implemented
+     * {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)}.
      *
      * @param handle the new phone number to dial
      * @param targetPhoneAccount the {@link PhoneAccountHandle} to use when placing the call.
      *                           If {@code null}, no change will be made to the
      *                           {@link PhoneAccountHandle} used to place the call.
+     * @param confirmFirst Telecom will ask users to confirm the redirection via a yes/no dialog
+     *                     if the confirmFirst is true, and if the redirection request of this
+     *                     response was sent with a true flag of allowInteractiveResponse via
+     *                     {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)}
      */
-    public final void redirectCall(Uri handle, PhoneAccountHandle targetPhoneAccount) {
+    public final void redirectCall(@NonNull Uri handle,
+                                   @NonNull PhoneAccountHandle targetPhoneAccount,
+                                   boolean confirmFirst) {
         try {
-            mCallRedirectionAdapter.redirectCall(handle, targetPhoneAccount);
+            mCallRedirectionAdapter.redirectCall(handle, targetPhoneAccount, confirmFirst);
         } catch (RemoteException e) {
         }
     }
 
     /**
      * The implemented {@link CallRedirectionService} calls this method to response a request
-     * received via {@link #onPlaceCall(Uri, PhoneAccountHandle)} to inform Telecom that an outgoing
-     * call should be canceled entirely.
+     * received via {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)} to inform Telecom that
+     * an outgoing call should be canceled entirely.
      *
-     * This can only be called from implemented {@link #onPlaceCall(Uri, PhoneAccountHandle)}.
+     * <p>This can only be called from implemented
+     * {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)}.
      *
      */
     public final void cancelCall() {
@@ -137,7 +161,8 @@
                     SomeArgs args = (SomeArgs) msg.obj;
                     try {
                         mCallRedirectionAdapter = (ICallRedirectionAdapter) args.arg1;
-                        onPlaceCall((Uri) args.arg2, (PhoneAccountHandle) args.arg3);
+                        onPlaceCall((Uri) args.arg2, (PhoneAccountHandle) args.arg3,
+                                (boolean) args.arg4);
                     } finally {
                         args.recycle();
                     }
@@ -152,15 +177,20 @@
          * Telecom calls this method to inform the CallRedirectionService of a new outgoing call
          * which is about to be placed.
          * @param handle the phone number dialed by the user
-         * @param targetPhoneAccount the URI of the number the user dialed
+         * @param initialPhoneAccount the URI of the number the user dialed
+         * @param allowInteractiveResponse a boolean to tell if the implemented
+         *                                 {@link CallRedirectionService} should allow interactive
+         *                                 responses with users.
          */
         @Override
-        public void placeCall(ICallRedirectionAdapter adapter, Uri handle,
-                              PhoneAccountHandle targetPhoneAccount) {
+        public void placeCall(@NonNull ICallRedirectionAdapter adapter, @NonNull Uri handle,
+                              @NonNull PhoneAccountHandle initialPhoneAccount,
+                              boolean allowInteractiveResponse) {
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = adapter;
             args.arg2 = handle;
-            args.arg3 = targetPhoneAccount;
+            args.arg3 = initialPhoneAccount;
+            args.arg4 = allowInteractiveResponse;
             mHandler.obtainMessage(MSG_PLACE_CALL, args).sendToTarget();
         }
     }
diff --git a/telecomm/java/com/android/internal/telecom/ICallRedirectionAdapter.aidl b/telecomm/java/com/android/internal/telecom/ICallRedirectionAdapter.aidl
index 46bf983..0a42a3f 100644
--- a/telecomm/java/com/android/internal/telecom/ICallRedirectionAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallRedirectionAdapter.aidl
@@ -31,5 +31,6 @@
 
     void placeCallUnmodified();
 
-    void redirectCall(in Uri handle, in PhoneAccountHandle targetPhoneAccount);
+    void redirectCall(in Uri handle, in PhoneAccountHandle targetPhoneAccount,
+            boolean confirmFirst);
 }
diff --git a/telecomm/java/com/android/internal/telecom/ICallRedirectionService.aidl b/telecomm/java/com/android/internal/telecom/ICallRedirectionService.aidl
index d8d360b..c1bc440 100644
--- a/telecomm/java/com/android/internal/telecom/ICallRedirectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallRedirectionService.aidl
@@ -30,5 +30,5 @@
  */
 oneway interface ICallRedirectionService {
     void placeCall(in ICallRedirectionAdapter adapter, in Uri handle,
-            in PhoneAccountHandle targetPhoneAccount);
+            in PhoneAccountHandle initialPhoneAccount, boolean allowInteractiveResponse);
 }
diff --git a/telephony/java/android/telephony/CellConfigLte.java b/telephony/java/android/telephony/CellConfigLte.java
index 35769f0..eafbfbc 100644
--- a/telephony/java/android/telephony/CellConfigLte.java
+++ b/telephony/java/android/telephony/CellConfigLte.java
@@ -34,6 +34,11 @@
     }
 
     /** @hide */
+    public CellConfigLte(android.hardware.radio.V1_4.CellConfigLte cellConfig) {
+        mIsEndcAvailable = cellConfig.isEndcAvailable;
+    }
+
+    /** @hide */
     public CellConfigLte(boolean isEndcAvailable) {
         mIsEndcAvailable = isEndcAvailable;
     }
diff --git a/telephony/java/android/telephony/CellInfo.java b/telephony/java/android/telephony/CellInfo.java
index b761bd7..8ce5c54 100644
--- a/telephony/java/android/telephony/CellInfo.java
+++ b/telephony/java/android/telephony/CellInfo.java
@@ -19,8 +19,10 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.UnsupportedAppUsage;
+import android.hardware.radio.V1_4.CellInfo.Info;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.SystemClock;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -318,6 +320,13 @@
     }
 
     /** @hide */
+    protected CellInfo(android.hardware.radio.V1_4.CellInfo ci) {
+        this.mRegistered = ci.isRegistered;
+        this.mTimeStamp = SystemClock.elapsedRealtimeNanos();
+        this.mCellConnectionStatus = ci.connectionStatus;
+    }
+
+    /** @hide */
     public static CellInfo create(android.hardware.radio.V1_0.CellInfo ci) {
         if (ci == null) return null;
         switch(ci.cellInfoType) {
@@ -342,4 +351,17 @@
             default: return null;
         }
     }
+
+    /** @hide */
+    public static CellInfo create(android.hardware.radio.V1_4.CellInfo ci) {
+        if (ci == null) return null;
+        switch (ci.info.getDiscriminator()) {
+            case Info.hidl_discriminator.gsm: return new CellInfoGsm(ci);
+            case Info.hidl_discriminator.cdma: return new CellInfoCdma(ci);
+            case Info.hidl_discriminator.lte: return new CellInfoLte(ci);
+            case Info.hidl_discriminator.wcdma: return new CellInfoWcdma(ci);
+            case Info.hidl_discriminator.tdscdma: return new CellInfoTdscdma(ci);
+            default: return null;
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/CellInfoCdma.java b/telephony/java/android/telephony/CellInfoCdma.java
index c9f07da..4440108 100644
--- a/telephony/java/android/telephony/CellInfoCdma.java
+++ b/telephony/java/android/telephony/CellInfoCdma.java
@@ -67,6 +67,15 @@
             new CellSignalStrengthCdma(cic.signalStrengthCdma, cic.signalStrengthEvdo);
     }
 
+    /** @hide */
+    public CellInfoCdma(android.hardware.radio.V1_4.CellInfo ci) {
+        super(ci);
+        final android.hardware.radio.V1_2.CellInfoCdma cic = ci.info.cdma();
+        mCellIdentityCdma = new CellIdentityCdma(cic.cellIdentityCdma);
+        mCellSignalStrengthCdma =
+                new CellSignalStrengthCdma(cic.signalStrengthCdma, cic.signalStrengthEvdo);
+    }
+
     @Override
     public CellIdentityCdma getCellIdentity() {
         return mCellIdentityCdma;
diff --git a/telephony/java/android/telephony/CellInfoGsm.java b/telephony/java/android/telephony/CellInfoGsm.java
index ad16dfa..248adfc 100644
--- a/telephony/java/android/telephony/CellInfoGsm.java
+++ b/telephony/java/android/telephony/CellInfoGsm.java
@@ -63,6 +63,14 @@
         mCellSignalStrengthGsm = new CellSignalStrengthGsm(cig.signalStrengthGsm);
     }
 
+    /** @hide */
+    public CellInfoGsm(android.hardware.radio.V1_4.CellInfo ci) {
+        super(ci);
+        final android.hardware.radio.V1_2.CellInfoGsm cig = ci.info.gsm();
+        mCellIdentityGsm = new CellIdentityGsm(cig.cellIdentityGsm);
+        mCellSignalStrengthGsm = new CellSignalStrengthGsm(cig.signalStrengthGsm);
+    }
+
     @Override
     public CellIdentityGsm getCellIdentity() {
         return mCellIdentityGsm;
diff --git a/telephony/java/android/telephony/CellInfoLte.java b/telephony/java/android/telephony/CellInfoLte.java
index 7593831..8e8ce8a 100644
--- a/telephony/java/android/telephony/CellInfoLte.java
+++ b/telephony/java/android/telephony/CellInfoLte.java
@@ -70,6 +70,15 @@
         mCellConfig = new CellConfigLte();
     }
 
+    /** @hide */
+    public CellInfoLte(android.hardware.radio.V1_4.CellInfo ci) {
+        super(ci);
+        final android.hardware.radio.V1_4.CellInfoLte cil = ci.info.lte();
+        mCellIdentityLte = new CellIdentityLte(cil.base.cellIdentityLte);
+        mCellSignalStrengthLte = new CellSignalStrengthLte(cil.base.signalStrengthLte);
+        mCellConfig = new CellConfigLte(cil.cellConfig);
+    }
+
     @Override
     public CellIdentityLte getCellIdentity() {
         if (DBG) log("getCellIdentity: " + mCellIdentityLte);
diff --git a/telephony/java/android/telephony/CellInfoTdscdma.java b/telephony/java/android/telephony/CellInfoTdscdma.java
index a8c49b7..2ab38fb 100644
--- a/telephony/java/android/telephony/CellInfoTdscdma.java
+++ b/telephony/java/android/telephony/CellInfoTdscdma.java
@@ -64,6 +64,14 @@
         mCellSignalStrengthTdscdma = new CellSignalStrengthTdscdma(cit.signalStrengthTdscdma);
     }
 
+    /** @hide */
+    public CellInfoTdscdma(android.hardware.radio.V1_4.CellInfo ci) {
+        super(ci);
+        final android.hardware.radio.V1_2.CellInfoTdscdma cit = ci.info.tdscdma();
+        mCellIdentityTdscdma = new CellIdentityTdscdma(cit.cellIdentityTdscdma);
+        mCellSignalStrengthTdscdma = new CellSignalStrengthTdscdma(cit.signalStrengthTdscdma);
+    }
+
     @Override public CellIdentityTdscdma getCellIdentity() {
         return mCellIdentityTdscdma;
     }
diff --git a/telephony/java/android/telephony/CellInfoWcdma.java b/telephony/java/android/telephony/CellInfoWcdma.java
index a427e80..65e0470 100644
--- a/telephony/java/android/telephony/CellInfoWcdma.java
+++ b/telephony/java/android/telephony/CellInfoWcdma.java
@@ -63,6 +63,14 @@
         mCellSignalStrengthWcdma = new CellSignalStrengthWcdma(ciw.signalStrengthWcdma);
     }
 
+    /** @hide */
+    public CellInfoWcdma(android.hardware.radio.V1_4.CellInfo ci) {
+        super(ci);
+        final android.hardware.radio.V1_2.CellInfoWcdma ciw = ci.info.wcdma();
+        mCellIdentityWcdma = new CellIdentityWcdma(ciw.cellIdentityWcdma);
+        mCellSignalStrengthWcdma = new CellSignalStrengthWcdma(ciw.signalStrengthWcdma);
+    }
+
     @Override
     public CellIdentityWcdma getCellIdentity() {
         return mCellIdentityWcdma;
diff --git a/test-base/api/current.txt b/test-base/api/current.txt
index 7ebd6aa..91fcca5 100644
--- a/test-base/api/current.txt
+++ b/test-base/api/current.txt
@@ -48,6 +48,9 @@
     method public abstract void startTiming(boolean);
   }
 
+  public abstract deprecated class RepetitiveTest implements java.lang.annotation.Annotation {
+  }
+
   public abstract deprecated class UiThreadTest implements java.lang.annotation.Annotation {
   }
 
diff --git a/test-base/src/android/test/RepetitiveTest.java b/test-base/src/android/test/RepetitiveTest.java
index 6a7130e..13e89d2 100644
--- a/test-base/src/android/test/RepetitiveTest.java
+++ b/test-base/src/android/test/RepetitiveTest.java
@@ -26,8 +26,10 @@
  * When the annotation is present, the test method is executed the number of times specified by
  * numIterations and defaults to 1.
  *
- * {@hide} Not needed for public API.
+ * @deprecated New tests should be written using the
+ * <a href="{@docRoot}tools/testing-support-library/index.html">Android Testing Support Library</a>.
  */
+@Deprecated
 @Target(ElementType.METHOD)
 @Retention(RetentionPolicy.RUNTIME)
 public @interface RepetitiveTest {
@@ -37,4 +39,4 @@
      * @return The total number of iterations, the default is 1.
      */
     int numIterations() default 1;
-}
\ No newline at end of file
+}
diff --git a/tests/net/java/android/net/LinkPropertiesTest.java b/tests/net/java/android/net/LinkPropertiesTest.java
index 932fee0..299fbef 100644
--- a/tests/net/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/java/android/net/LinkPropertiesTest.java
@@ -849,6 +849,18 @@
         assertEquals(new ArraySet<>(expectRemoved), (new ArraySet<>(result.removed)));
     }
 
+    private void assertParcelingIsLossless(LinkProperties source) {
+        Parcel p = Parcel.obtain();
+        source.writeToParcel(p, /* flags */ 0);
+        p.setDataPosition(0);
+        final byte[] marshalled = p.marshall();
+        p = Parcel.obtain();
+        p.unmarshall(marshalled, 0, marshalled.length);
+        p.setDataPosition(0);
+        LinkProperties dest = LinkProperties.CREATOR.createFromParcel(p);
+        assertEquals(source, dest);
+    }
+
     @Test
     public void testLinkPropertiesParcelable() throws Exception {
         LinkProperties source = new LinkProperties();
@@ -870,15 +882,12 @@
 
         source.setNat64Prefix(new IpPrefix("2001:db8:1:2:64:64::/96"));
 
-        Parcel p = Parcel.obtain();
-        source.writeToParcel(p, /* flags */ 0);
-        p.setDataPosition(0);
-        final byte[] marshalled = p.marshall();
-        p = Parcel.obtain();
-        p.unmarshall(marshalled, 0, marshalled.length);
-        p.setDataPosition(0);
-        LinkProperties dest = LinkProperties.CREATOR.createFromParcel(p);
+        assertParcelingIsLossless(source);
+    }
 
-        assertEquals(source, dest);
+    @Test
+    public void testParcelUninitialized() throws Exception {
+        LinkProperties empty = new LinkProperties();
+        assertParcelingIsLossless(empty);
     }
 }
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index bf39644..2a92a7d 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -4683,7 +4683,7 @@
         mCellNetworkAgent.sendLinkProperties(cellLp);
         mCellNetworkAgent.connect(true);
         networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        verify(mNetworkManagementService, times(1)).startClatd(MOBILE_IFNAME);
+        verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME);
         Nat464Xlat clat = mService.getNat464Xlat(mCellNetworkAgent);
 
         // Clat iface up, expect stack link updated.
@@ -4710,7 +4710,7 @@
         mCellNetworkAgent.sendLinkProperties(cellLp);
         waitForIdle();
         networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
-        verify(mNetworkManagementService, times(1)).stopClatd(MOBILE_IFNAME);
+        verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME);
 
         // Clat iface removed, expect linkproperties revert to original one
         clat.interfaceRemoved(CLAT_PREFIX + MOBILE_IFNAME);
diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
index 4c52d81..9578ded 100644
--- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
@@ -32,11 +32,13 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.net.ConnectivityManager;
+import android.net.INetd;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.NetworkMisc;
 import android.net.NetworkStack;
+import android.os.INetworkManagementService;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.text.format.DateUtils;
@@ -66,6 +68,8 @@
     LingerMonitor mMonitor;
 
     @Mock ConnectivityService mConnService;
+    @Mock INetd mNetd;
+    @Mock INetworkManagementService mNMS;
     @Mock Context mCtx;
     @Mock NetworkMisc mMisc;
     @Mock NetworkNotificationManager mNotifier;
@@ -352,7 +356,7 @@
         caps.addCapability(0);
         caps.addTransportType(transport);
         NetworkAgentInfo nai = new NetworkAgentInfo(null, null, new Network(netId), info, null,
-                caps, 50, mCtx, null, mMisc, mConnService);
+                caps, 50, mCtx, null, mMisc, mConnService, mNetd, mNMS);
         nai.everValidated = true;
         return nai;
     }
diff --git a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
index bf42412..07b1d05 100644
--- a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
+++ b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
@@ -17,9 +17,7 @@
 package com.android.server.connectivity;
 
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -27,6 +25,7 @@
 import static org.mockito.Mockito.when;
 
 import android.net.ConnectivityManager;
+import android.net.INetd;
 import android.net.InterfaceConfiguration;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
@@ -57,6 +56,7 @@
 
     @Mock ConnectivityService mConnectivity;
     @Mock NetworkMisc mMisc;
+    @Mock INetd mNetd;
     @Mock INetworkManagementService mNms;
     @Mock InterfaceConfiguration mConfig;
     @Mock NetworkAgentInfo mNai;
@@ -65,7 +65,7 @@
     Handler mHandler;
 
     Nat464Xlat makeNat464Xlat() {
-        return new Nat464Xlat(mNms, mNai);
+        return new Nat464Xlat(mNai, mNetd, mNms);
     }
 
     @Before
@@ -129,7 +129,7 @@
         nat.start();
 
         verify(mNms).registerObserver(eq(nat));
-        verify(mNms).startClatd(eq(BASE_IFACE));
+        verify(mNetd).clatdStart(eq(BASE_IFACE));
 
         // Stacked interface up notification arrives.
         nat.interfaceLinkStateChanged(STACKED_IFACE, true);
@@ -144,7 +144,7 @@
         // ConnectivityService stops clat (Network disconnects, IPv4 addr appears, ...).
         nat.stop();
 
-        verify(mNms).stopClatd(eq(BASE_IFACE));
+        verify(mNetd).clatdStop(eq(BASE_IFACE));
 
         // Stacked interface removed notification arrives.
         nat.interfaceRemoved(STACKED_IFACE);
@@ -156,7 +156,7 @@
         assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
         assertIdle(nat);
 
-        verifyNoMoreInteractions(mNms, mConnectivity);
+        verifyNoMoreInteractions(mNetd, mNms, mConnectivity);
     }
 
     @Test
@@ -168,7 +168,7 @@
         nat.start();
 
         verify(mNms).registerObserver(eq(nat));
-        verify(mNms).startClatd(eq(BASE_IFACE));
+        verify(mNetd).clatdStart(eq(BASE_IFACE));
 
         // Stacked interface up notification arrives.
         nat.interfaceLinkStateChanged(STACKED_IFACE, true);
@@ -185,7 +185,7 @@
         mLooper.dispatchNext();
 
         verify(mNms).unregisterObserver(eq(nat));
-        verify(mNms).stopClatd(eq(BASE_IFACE));
+        verify(mNetd).clatdStop(eq(BASE_IFACE));
         verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture());
         assertTrue(c.getValue().getStackedLinks().isEmpty());
         assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
@@ -194,7 +194,7 @@
         // ConnectivityService stops clat: no-op.
         nat.stop();
 
-        verifyNoMoreInteractions(mNms, mConnectivity);
+        verifyNoMoreInteractions(mNetd, mNms, mConnectivity);
     }
 
     @Test
@@ -205,13 +205,13 @@
         nat.start();
 
         verify(mNms).registerObserver(eq(nat));
-        verify(mNms).startClatd(eq(BASE_IFACE));
+        verify(mNetd).clatdStart(eq(BASE_IFACE));
 
         // ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...)
         nat.stop();
 
         verify(mNms).unregisterObserver(eq(nat));
-        verify(mNms).stopClatd(eq(BASE_IFACE));
+        verify(mNetd).clatdStop(eq(BASE_IFACE));
         assertIdle(nat);
 
         // In-flight interface up notification arrives: no-op
@@ -225,7 +225,7 @@
 
         assertIdle(nat);
 
-        verifyNoMoreInteractions(mNms, mConnectivity);
+        verifyNoMoreInteractions(mNetd, mNms, mConnectivity);
     }
 
     @Test
@@ -236,16 +236,16 @@
         nat.start();
 
         verify(mNms).registerObserver(eq(nat));
-        verify(mNms).startClatd(eq(BASE_IFACE));
+        verify(mNetd).clatdStart(eq(BASE_IFACE));
 
         // ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...)
         nat.stop();
 
         verify(mNms).unregisterObserver(eq(nat));
-        verify(mNms).stopClatd(eq(BASE_IFACE));
+        verify(mNetd).clatdStop(eq(BASE_IFACE));
         assertIdle(nat);
 
-        verifyNoMoreInteractions(mNms, mConnectivity);
+        verifyNoMoreInteractions(mNetd, mNms, mConnectivity);
     }
 
     static void assertIdle(Nat464Xlat nat) {
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
index 859a54d..e63c3b0 100644
--- a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
+++ b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -16,6 +16,9 @@
 
 package com.android.server.net.ipmemorystore;
 
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+
 import android.content.Context;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -26,6 +29,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.io.File;
+
 /** Unit tests for {@link IpMemoryStoreServiceTest}. */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -36,6 +41,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        doReturn(new File("/tmp/test.db")).when(mMockContext).getDatabasePath(anyString());
     }
 
     @Test
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/RelevanceUtilsTests.java b/tests/net/java/com/android/server/net/ipmemorystore/RelevanceUtilsTests.java
new file mode 100644
index 0000000..8d367e2
--- /dev/null
+++ b/tests/net/java/com/android/server/net/ipmemorystore/RelevanceUtilsTests.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.ipmemorystore;
+
+import static com.android.server.net.ipmemorystore.RelevanceUtils.CAPPED_RELEVANCE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link RelevanceUtils}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RelevanceUtilsTests {
+    @Test
+    public void testComputeRelevanceForTargetDate() {
+        final long dayInMillis = 24L * 60 * 60 * 1000;
+        final long base = 1_000_000L; // any given point in time
+        // Relevance when the network expires in 1000 years must be capped
+        assertEquals(CAPPED_RELEVANCE, RelevanceUtils.computeRelevanceForTargetDate(
+                base + 1000L * dayInMillis, base));
+        // Relevance when expiry is before the date must be 0
+        assertEquals(0, RelevanceUtils.computeRelevanceForTargetDate(base - 1, base));
+        // Make sure the relevance for a given target date is higher if the expiry is further
+        // in the future
+        assertTrue(RelevanceUtils.computeRelevanceForTargetDate(base + 100 * dayInMillis, base)
+                < RelevanceUtils.computeRelevanceForTargetDate(base + 150 * dayInMillis, base));
+
+        // Make sure the relevance falls slower as the expiry is closing in. This is to ensure
+        // the decay is indeed logarithmic.
+        final int relevanceAtExpiry = RelevanceUtils.computeRelevanceForTargetDate(base, base);
+        final int relevance50DaysBeforeExpiry =
+                RelevanceUtils.computeRelevanceForTargetDate(base + 50 * dayInMillis, base);
+        final int relevance100DaysBeforeExpiry =
+                RelevanceUtils.computeRelevanceForTargetDate(base + 100 * dayInMillis, base);
+        final int relevance150DaysBeforeExpiry =
+                RelevanceUtils.computeRelevanceForTargetDate(base + 150 * dayInMillis, base);
+        assertEquals(0, relevanceAtExpiry);
+        assertTrue(relevance50DaysBeforeExpiry - relevanceAtExpiry
+                < relevance100DaysBeforeExpiry - relevance50DaysBeforeExpiry);
+        assertTrue(relevance100DaysBeforeExpiry - relevance50DaysBeforeExpiry
+                < relevance150DaysBeforeExpiry - relevance100DaysBeforeExpiry);
+    }
+
+    @Test
+    public void testIncreaseRelevance() {
+        long expiry = System.currentTimeMillis();
+
+        final long firstBump = RelevanceUtils.bumpExpiryDate(expiry);
+        // Though a few milliseconds might have elapsed, the first bump should push the duration
+        // to days in the future, so unless this test takes literal days between these two lines,
+        // this should always pass.
+        assertTrue(firstBump > expiry);
+
+        expiry = 0;
+        long lastDifference = Long.MAX_VALUE;
+        // The relevance should be capped in at most this many steps. Otherwise, fail.
+        final int steps = 1000;
+        for (int i = 0; i < steps; ++i) {
+            final long newExpiry = RelevanceUtils.bumpExpiryDuration(expiry);
+            if (newExpiry == expiry) {
+                // The relevance should be capped. Make sure it is, then exit without failure.
+                assertEquals(newExpiry, RelevanceUtils.CAPPED_RELEVANCE_LIFETIME_MS);
+                return;
+            }
+            // Make sure the new expiry is further in the future than last time.
+            assertTrue(newExpiry > expiry);
+            // Also check that it was not bumped as much as the last bump, because the
+            // decay must be exponential.
+            assertTrue(newExpiry - expiry < lastDifference);
+            lastDifference = newExpiry - expiry;
+            expiry = newExpiry;
+        }
+        fail("Relevance failed to go to the maximum value after " + steps + " bumps");
+    }
+
+    @Test
+    public void testContinuity() {
+        final long expiry = System.currentTimeMillis();
+
+        // Relevance at expiry and after expiry should be the cap.
+        final int relevanceBeforeMaxLifetime = RelevanceUtils.computeRelevanceForTargetDate(expiry,
+                expiry - (RelevanceUtils.CAPPED_RELEVANCE_LIFETIME_MS + 1_000_000));
+        assertEquals(relevanceBeforeMaxLifetime, CAPPED_RELEVANCE);
+        final int relevanceForMaxLifetime = RelevanceUtils.computeRelevanceForTargetDate(expiry,
+                expiry - RelevanceUtils.CAPPED_RELEVANCE_LIFETIME_MS);
+        assertEquals(relevanceForMaxLifetime, CAPPED_RELEVANCE);
+
+        // If the max relevance is reached at the cap lifetime, one millisecond less than this
+        // should be very close. Strictly speaking this is a bit brittle, but it should be
+        // good enough for the purposes of the memory store.
+        final int relevanceForOneMillisecLessThanCap = RelevanceUtils.computeRelevanceForTargetDate(
+                expiry, expiry - RelevanceUtils.CAPPED_RELEVANCE_LIFETIME_MS + 1);
+        assertTrue(relevanceForOneMillisecLessThanCap <= CAPPED_RELEVANCE);
+        assertTrue(relevanceForOneMillisecLessThanCap >= CAPPED_RELEVANCE - 10);
+
+        // Likewise the relevance one millisecond before expiry should be very close to 0. It's
+        // fine if it rounds down to 0.
+        final int relevanceOneMillisecBeforeExpiry = RelevanceUtils.computeRelevanceForTargetDate(
+                expiry, expiry - 1);
+        assertTrue(relevanceOneMillisecBeforeExpiry <= 10);
+        assertTrue(relevanceOneMillisecBeforeExpiry >= 0);
+
+        final int relevanceAtExpiry = RelevanceUtils.computeRelevanceForTargetDate(expiry, expiry);
+        assertEquals(relevanceAtExpiry, 0);
+        final int relevanceAfterExpiry = RelevanceUtils.computeRelevanceForTargetDate(expiry,
+                expiry + 1_000_000);
+        assertEquals(relevanceAfterExpiry, 0);
+    }
+
+    // testIncreaseRelevance makes sure bumping the expiry continuously always yields a
+    // monotonically increasing date as a side effect, but this tests that the relevance (as
+    // opposed to the expiry date) increases monotonically with increasing periods.
+    @Test
+    public void testMonotonicity() {
+        // Hopefully the relevance is granular enough to give a different value for every one
+        // of this number of steps.
+        final int steps = 40;
+        final long expiry = System.currentTimeMillis();
+
+        int lastRelevance = -1;
+        for (int i = 0; i < steps; ++i) {
+            final long date = expiry - i * (RelevanceUtils.CAPPED_RELEVANCE_LIFETIME_MS / steps);
+            final int relevance = RelevanceUtils.computeRelevanceForTargetDate(expiry, date);
+            assertTrue(relevance > lastRelevance);
+            lastRelevance = relevance;
+        }
+    }
+}