Merge changes Idbde7000,Idde3c9d0 into nyc-mr2-dev-plus-aosp

* changes:
  DO NOT MERGE ApfFilter: use elapsedRealTime for RA lifetime
  DO NOT MERGE ApfFilter: systematically use u8, u16, u32 getters
diff --git a/api/system-current.txt b/api/system-current.txt
index 586bcfb..1fcdf800 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -25762,6 +25762,8 @@
     method public android.net.wifi.WifiConfiguration[] getConnectableConfigs();
     method public android.net.wifi.WifiConfiguration getConnectedConfig();
     method public android.net.wifi.WifiConfiguration getDefaultWifiConfig();
+    method public int getLastSelectedNetworkId();
+    method public long getLastSelectedNetworkTimestamp();
     method public android.net.wifi.ScanResult[] getScanResults();
     method public void setConnectableConfigs(android.net.wifi.WifiConfiguration[]);
     method public void setConnectedConfig(android.net.wifi.WifiConfiguration);
@@ -25775,6 +25777,7 @@
     method public android.net.RecommendationRequest.Builder setConnectableConfigs(android.net.wifi.WifiConfiguration[]);
     method public android.net.RecommendationRequest.Builder setConnectedWifiConfig(android.net.wifi.WifiConfiguration);
     method public android.net.RecommendationRequest.Builder setDefaultWifiConfig(android.net.wifi.WifiConfiguration);
+    method public android.net.RecommendationRequest.Builder setLastSelectedNetwork(int, long);
     method public android.net.RecommendationRequest.Builder setScanResults(android.net.wifi.ScanResult[]);
   }
 
@@ -35484,6 +35487,7 @@
     field public static final java.lang.String BOOT_COUNT = "boot_count";
     field public static final java.lang.String CONTACT_METADATA_SYNC_ENABLED = "contact_metadata_sync_enabled";
     field public static final android.net.Uri CONTENT_URI;
+    field public static final java.lang.String CURATE_SAVED_OPEN_NETWORKS = "curate_saved_open_networks";
     field public static final java.lang.String DATA_ROAMING = "data_roaming";
     field public static final java.lang.String DEBUG_APP = "debug_app";
     field public static final java.lang.String DEVELOPMENT_SETTINGS_ENABLED = "development_settings_enabled";
diff --git a/core/java/android/net/RecommendationRequest.java b/core/java/android/net/RecommendationRequest.java
index a227f18..b89a245 100644
--- a/core/java/android/net/RecommendationRequest.java
+++ b/core/java/android/net/RecommendationRequest.java
@@ -37,6 +37,8 @@
     private final WifiConfiguration mDefaultConfig;
     private WifiConfiguration mConnectedConfig;
     private WifiConfiguration[] mConnectableConfigs;
+    private final int mLastSelectedNetworkId;
+    private final long mLastSelectedNetworkTimestamp;
 
     /**
      * Builder class for constructing {@link RecommendationRequest} instances.
@@ -48,17 +50,9 @@
         private WifiConfiguration mDefaultConfig;
         private WifiConfiguration mConnectedConfig;
         private WifiConfiguration[] mConnectableConfigs;
+        private int mLastSelectedNetworkId;
+        private long mLastSelectedTimestamp;
 
-        /**
-         * @param scanResults the array of {@link ScanResult}s the recommendation must be
-         *                    constrained to i.e. if a non-null wifi config recommendation is
-         *                    returned then it must be able to connect to one of the networks in
-         *                    the results list.
-         *
-         *                    If the array is {@code null} or empty then there is no constraint.
-         *
-         * @return this
-         */
         public Builder setScanResults(ScanResult[] scanResults) {
             mScanResults = scanResults;
             return this;
@@ -89,7 +83,20 @@
          * @return this
          */
         public Builder setConnectableConfigs(WifiConfiguration[] connectableConfigs) {
-            mConnectableConfigs = connectableConfigs;
+            this.mConnectableConfigs = connectableConfigs;
+            return this;
+        }
+
+        /**
+         * @param networkId The {@link WifiConfiguration#networkId} of the last user selected
+         *                  network.
+         * @param timestamp The {@link android.os.SystemClock#elapsedRealtime()} when the user
+         *                  selected {@code networkId}.
+         * @return this
+         */
+        public Builder setLastSelectedNetwork(int networkId, long timestamp) {
+            this.mLastSelectedNetworkId = networkId;
+            this.mLastSelectedTimestamp = timestamp;
             return this;
         }
 
@@ -97,10 +104,8 @@
          * @return a new {@link RecommendationRequest} instance
          */
         public RecommendationRequest build() {
-            return new RecommendationRequest(mScanResults,
-                    mDefaultConfig,
-                    mConnectedConfig,
-                    mConnectableConfigs);
+            return new RecommendationRequest(mScanResults, mDefaultConfig, mConnectedConfig,
+                    mConnectableConfigs, mLastSelectedNetworkId, mLastSelectedTimestamp);
         }
     }
 
@@ -154,15 +159,35 @@
         mConnectableConfigs = connectableConfigs;
     }
 
+    /**
+     * @return The {@link WifiConfiguration#networkId} of the last user selected network.
+     *         {@code 0} if not set.
+     */
+    public int getLastSelectedNetworkId() {
+        return mLastSelectedNetworkId;
+    }
+
+    /**
+     * @return The {@link android.os.SystemClock#elapsedRealtime()} when the user selected
+     *         {@link #getLastSelectedNetworkId()}. {@code 0} if not set.
+     */
+    public long getLastSelectedNetworkTimestamp() {
+        return mLastSelectedNetworkTimestamp;
+    }
+
     @VisibleForTesting
     RecommendationRequest(ScanResult[] scanResults,
             WifiConfiguration defaultWifiConfig,
             WifiConfiguration connectedWifiConfig,
-            WifiConfiguration[] connectableConfigs) {
+            WifiConfiguration[] connectableConfigs,
+            int lastSelectedNetworkId,
+            long lastSelectedNetworkTimestamp) {
         mScanResults = scanResults;
         mDefaultConfig = defaultWifiConfig;
         mConnectedConfig = connectedWifiConfig;
         mConnectableConfigs = connectableConfigs;
+        mLastSelectedNetworkId = lastSelectedNetworkId;
+        mLastSelectedNetworkTimestamp = lastSelectedNetworkTimestamp;
     }
 
     protected RecommendationRequest(Parcel in) {
@@ -190,6 +215,9 @@
         } else {
             mConnectableConfigs = null;
         }
+
+        mLastSelectedNetworkId = in.readInt();
+        mLastSelectedNetworkTimestamp = in.readLong();
     }
 
     @Override
@@ -220,7 +248,8 @@
             dest.writeInt(0);
         }
 
-
+        dest.writeInt(mLastSelectedNetworkId);
+        dest.writeLong(mLastSelectedNetworkTimestamp);
     }
 
     public static final Creator<RecommendationRequest> CREATOR =
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 4b130ed1..20de370 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.opengl.EGL14;
 import android.os.SystemProperties;
 import android.util.Log;
 
@@ -34,6 +35,23 @@
     private static final String PROPERTY_GFX_DRIVER = "ro.gfx.driver.0";
 
     public static void setupGraphicsEnvironment(Context context) {
+        chooseDriver(context);
+
+        // Now that we've figured out which driver to use for this process, load and initialize it.
+        // This can take multiple frame periods, and it would otherwise happen as part of the first
+        // frame, increasing first-frame latency. Starting it here, as a low-priority background
+        // thread, means that it's usually done long before we start drawing the first frame,
+        // without significantly disrupting other activity launch work.
+        Thread eglInitThread = new Thread(
+                () -> {
+                    Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
+                    EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
+                },
+                "EGL Init");
+        eglInitThread.start();
+    }
+
+    private static void chooseDriver(Context context) {
         String driverPackageName = SystemProperties.get(PROPERTY_GFX_DRIVER);
         if (driverPackageName == null || driverPackageName.isEmpty()) {
             return;
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index f6e6ad6..b5ab908 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -27,6 +27,8 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
+import libcore.util.SneakyThrow;
+
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.FileDescriptor;
@@ -249,6 +251,7 @@
     private static final int EX_NETWORK_MAIN_THREAD = -6;
     private static final int EX_UNSUPPORTED_OPERATION = -7;
     private static final int EX_SERVICE_SPECIFIC = -8;
+    private static final int EX_PARCELABLE = -9;
     private static final int EX_HAS_REPLY_HEADER = -128;  // special; see below
     // EX_TRANSACTION_FAILED is used exclusively in native code.
     // see libbinder's binder/Status.h
@@ -1555,7 +1558,12 @@
      */
     public final void writeException(Exception e) {
         int code = 0;
-        if (e instanceof SecurityException) {
+        if (e instanceof Parcelable
+                && (e.getClass().getClassLoader() == Parcelable.class.getClassLoader())) {
+            // We only send Parcelable exceptions that are in the
+            // BootClassLoader to ensure that the receiver can unpack them
+            code = EX_PARCELABLE;
+        } else if (e instanceof SecurityException) {
             code = EX_SECURITY;
         } else if (e instanceof BadParcelableException) {
             code = EX_BAD_PARCELABLE;
@@ -1581,8 +1589,20 @@
             throw new RuntimeException(e);
         }
         writeString(e.getMessage());
-        if (e instanceof ServiceSpecificException) {
-            writeInt(((ServiceSpecificException)e).errorCode);
+        switch (code) {
+            case EX_SERVICE_SPECIFIC:
+                writeInt(((ServiceSpecificException) e).errorCode);
+                break;
+            case EX_PARCELABLE:
+                // Write parceled exception prefixed by length
+                final int sizePosition = dataPosition();
+                writeInt(0);
+                writeParcelable((Parcelable) e, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+                final int payloadPosition = dataPosition();
+                setDataPosition(sizePosition);
+                writeInt(payloadPosition - sizePosition);
+                setDataPosition(payloadPosition);
+                break;
         }
     }
 
@@ -1680,6 +1700,13 @@
      */
     public final void readException(int code, String msg) {
         switch (code) {
+            case EX_PARCELABLE:
+                if (readInt() > 0) {
+                    SneakyThrow.sneakyThrow(
+                            (Exception) readParcelable(Parcelable.class.getClassLoader()));
+                } else {
+                    throw new RuntimeException(msg + " [missing Parcelable]");
+                }
             case EX_SECURITY:
                 throw new SecurityException(msg);
             case EX_BAD_PARCELABLE:
diff --git a/core/java/android/os/ParcelableException.java b/core/java/android/os/ParcelableException.java
new file mode 100644
index 0000000..d84d629
--- /dev/null
+++ b/core/java/android/os/ParcelableException.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import java.io.IOException;
+
+/**
+ * Wrapper class that offers to transport typical {@link Throwable} across a
+ * {@link Binder} call. This class is typically used to transport exceptions
+ * that cannot be modified to add {@link Parcelable} behavior, such as
+ * {@link IOException}.
+ * <ul>
+ * <li>The wrapped throwable must be defined as system class (that is, it must
+ * be in the same {@link ClassLoader} as {@link Parcelable}).
+ * <li>The wrapped throwable must support the
+ * {@link Throwable#Throwable(String)} constructor.
+ * <li>The receiver side must catch any thrown {@link ParcelableException} and
+ * call {@link #maybeRethrow(Class)} for all expected exception types.
+ * </ul>
+ *
+ * @hide
+ */
+public final class ParcelableException extends RuntimeException implements Parcelable {
+    public ParcelableException(Throwable t) {
+        super(t);
+    }
+
+    @SuppressWarnings("unchecked")
+    public <T extends Throwable> void maybeRethrow(Class<T> clazz) throws T {
+        if (clazz.isAssignableFrom(getCause().getClass())) {
+            throw (T) getCause();
+        }
+    }
+
+    /** {@hide} */
+    public static Throwable readFromParcel(Parcel in) {
+        final String name = in.readString();
+        final String msg = in.readString();
+        try {
+            final Class<?> clazz = Class.forName(name, true, Parcelable.class.getClassLoader());
+            return (Throwable) clazz.getConstructor(String.class).newInstance(msg);
+        } catch (ReflectiveOperationException e) {
+            throw new RuntimeException(name + ": " + msg);
+        }
+    }
+
+    /** {@hide} */
+    public static void writeToParcel(Parcel out, Throwable t) {
+        out.writeString(t.getClass().getName());
+        out.writeString(t.getMessage());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        writeToParcel(dest, getCause());
+    }
+
+    public static final Creator<ParcelableException> CREATOR = new Creator<ParcelableException>() {
+        @Override
+        public ParcelableException createFromParcel(Parcel source) {
+            return new ParcelableException(readFromParcel(source));
+        }
+
+        @Override
+        public ParcelableException[] newArray(int size) {
+            return new ParcelableException[size];
+        }
+    };
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index eb04e54..896dcb1 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7696,6 +7696,16 @@
                 "network_recommendations_enabled";
 
         /**
+         * Value to specify if the Wi-Fi Framework should defer to
+         * {@link com.android.server.NetworkScoreService} for evaluating saved open networks.
+         *
+         * Type: int (0 for false, 1 for true)
+         * @hide
+         */
+        @SystemApi
+        public static final String CURATE_SAVED_OPEN_NETWORKS = "curate_saved_open_networks";
+
+        /**
          * The number of milliseconds the {@link com.android.server.NetworkScoreService}
          * will give a recommendation request to complete before returning a default response.
          *
diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java
index 5099eeb..7d5941e 100644
--- a/core/java/android/provider/VoicemailContract.java
+++ b/core/java/android/provider/VoicemailContract.java
@@ -30,6 +30,7 @@
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.Voicemail;
+
 import java.util.List;
 
 /**
@@ -318,6 +319,48 @@
         public static final String LAST_MODIFIED = "last_modified";
 
         /**
+         * Flag to indicate the voicemail was backed up. The value will be 1 if backed up, 0 if
+         * not.
+         *
+         * <P>Type: INTEGER (boolean)</P>
+         *
+         * @hide
+         */
+        public static final String BACKED_UP = "backed_up";
+
+        /**
+         * Flag to indicate the voicemail was restored from a backup. The value will be 1 if
+         * restored, 0 if not.
+         *
+         * <P>Type: INTEGER (boolean)</P>
+         *
+         * @hide
+         */
+        public static final String RESTORED = "restored";
+
+        /**
+         * Flag to indicate the voicemail was marked as archived. Archived voicemail should not be
+         * deleted even if it no longer exist on the server. The value will be 1 if archived true, 0
+         * if not.
+         *
+         * <P>Type: INTEGER (boolean)</P>
+         *
+         * @hide
+         */
+        public static final String ARCHIVED = "archived";
+
+        /**
+         * Flag to indicate the voicemail is a OMTP voicemail handled by the {@link
+         * android.telephony.VisualVoicemailService}. The UI should only show OMTP voicemails from
+         * the current visual voicemail package.
+         *
+         * <P>Type: INTEGER (boolean)</P>
+         *
+         * @hide
+         */
+        public static final String IS_OMTP_VOICEMAIL = "is_omtp_voicmail";
+
+        /**
          * A convenience method to build voicemail URI specific to a source package by appending
          * {@link VoicemailContract#PARAM_KEY_SOURCE_PACKAGE} param to the base URI.
          */
diff --git a/core/java/android/util/ExceptionUtils.java b/core/java/android/util/ExceptionUtils.java
index f5d515d..da0b609 100644
--- a/core/java/android/util/ExceptionUtils.java
+++ b/core/java/android/util/ExceptionUtils.java
@@ -16,6 +16,8 @@
 
 package android.util;
 
+import android.os.ParcelableException;
+
 import java.io.IOException;
 
 /**
@@ -24,19 +26,13 @@
  * @hide
  */
 public class ExceptionUtils {
-    // TODO: longer term these should be replaced with first-class
-    // Parcel.read/writeException() and AIDL support, but for now do this using
-    // a nasty hack.
-
-    private static final String PREFIX_IO = "\u2603";
-
     public static RuntimeException wrap(IOException e) {
-        throw new IllegalStateException(PREFIX_IO + e.getMessage());
+        throw new ParcelableException(e);
     }
 
     public static void maybeUnwrapIOException(RuntimeException e) throws IOException {
-        if ((e instanceof IllegalStateException) && e.getMessage().startsWith(PREFIX_IO)) {
-            throw new IOException(e.getMessage().substring(PREFIX_IO.length()));
+        if (e instanceof ParcelableException) {
+            ((ParcelableException) e).maybeRethrow(IOException.class);
         }
     }
 
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index ef5231c..6ad7667 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -25,7 +25,6 @@
 import android.icu.text.DecimalFormatSymbols;
 import android.icu.util.ULocale;
 import android.net.LocalServerSocket;
-import android.opengl.EGL14;
 import android.os.IInstalld;
 import android.os.Process;
 import android.os.RemoteException;
@@ -121,9 +120,6 @@
         Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadResources");
         preloadResources();
         Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
-        Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadOpenGL");
-        preloadOpenGL();
-        Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
         preloadSharedLibraries();
         preloadTextResources();
         // Ask the WebViewFactory to do any initialization that must run in the zygote process,
@@ -164,12 +160,6 @@
         System.loadLibrary("jnigraphics");
     }
 
-    private static void preloadOpenGL() {
-        if (!SystemProperties.getBoolean(PROPERTY_DISABLE_OPENGL_PRELOADING, false)) {
-            EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
-        }
-    }
-
     private static void preloadTextResources() {
         Hyphenator.init();
         TextView.preloadFontCache();
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 3039d4f..c2a4800 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -395,7 +395,7 @@
     <string name="network_logging_notification_title">Network traffic is being monitored</string>
     <!-- Content text for a notification. Tapping opens a dialog with more information on network
         logging. [CHAR LIMIT=NONE]-->
-    <string name="network_logging_notification_text">Tap for more details</string>
+    <string name="network_logging_notification_text">Tap to learn more</string>
 
     <!-- Factory reset warning dialog strings--> <skip />
     <!-- Shows up in the dialog's title to warn about an impeding factory reset. [CHAR LIMIT=NONE] -->
diff --git a/core/tests/coretests/src/android/net/RecommendationRequestTest.java b/core/tests/coretests/src/android/net/RecommendationRequestTest.java
index 39c1691..bd25500 100644
--- a/core/tests/coretests/src/android/net/RecommendationRequestTest.java
+++ b/core/tests/coretests/src/android/net/RecommendationRequestTest.java
@@ -3,6 +3,7 @@
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.os.Parcel;
+import android.os.SystemClock;
 import android.test.AndroidTestCase;
 
 public class RecommendationRequestTest extends AndroidTestCase {
@@ -10,6 +11,8 @@
     private WifiConfiguration mDefaultConfig;
     private WifiConfiguration mConnectedConfig;
     private WifiConfiguration[] mConnectableConfigs;
+    private int mLastSelectedNetworkId;
+    private long mLastSelectedNetworkTimestamp;
 
     @Override
     public void setUp() throws Exception {
@@ -35,6 +38,8 @@
         mConnectedConfig = new WifiConfiguration();
         mConnectedConfig.SSID = "connected_config";
         mConnectableConfigs = new WifiConfiguration[] {mDefaultConfig, mConnectedConfig};
+        mLastSelectedNetworkId = 5;
+        mLastSelectedNetworkTimestamp = SystemClock.elapsedRealtime();
     }
 
     public void testParceling() throws Exception {
@@ -43,6 +48,7 @@
                 .setScanResults(mScanResults)
                 .setConnectedWifiConfig(mConnectedConfig)
                 .setConnectableConfigs(mConnectableConfigs)
+                .setLastSelectedNetwork(mLastSelectedNetworkId, mLastSelectedNetworkTimestamp)
                 .build();
 
         RecommendationRequest parceled = passThroughParcel(request);
@@ -60,6 +66,8 @@
         for (int i = 0; i < parceledConfigs.length; i++) {
             assertEquals(mConnectableConfigs[i].SSID, parceledConfigs[i].SSID);
         }
+        assertEquals(mLastSelectedNetworkId, parceled.getLastSelectedNetworkId());
+        assertEquals(mLastSelectedNetworkTimestamp, parceled.getLastSelectedNetworkTimestamp());
     }
 
     public void testParceling_nullScanResults() throws Exception {
@@ -82,6 +90,16 @@
         assertNull(parceledConfigs);
     }
 
+    public void testParceling_unsetLastSelectedNetwork() throws Exception {
+        RecommendationRequest request = new RecommendationRequest.Builder()
+                .build();
+
+        RecommendationRequest parceled = passThroughParcel(request);
+
+        assertEquals(0, parceled.getLastSelectedNetworkId());
+        assertEquals(0, parceled.getLastSelectedNetworkTimestamp());
+    }
+
     private RecommendationRequest passThroughParcel(RecommendationRequest request) {
         Parcel p = Parcel.obtain();
         RecommendationRequest output = null;
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 3627c29..bd3bfbc 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -891,4 +891,10 @@
     <!-- Content description for drawer menu button [CHAR_LIMIT=30]-->
     <string name="content_description_menu_button">Menu</string>
 
+    <!-- Label for carrier demo mode factory reset confirmation dialog. [CHAR LIMIT=40] -->
+    <string name="retail_demo_reset_message">Enter password to perform factory reset in demo mode</string>
+    <!-- Label for positive button on carrier demo  mode factory reset confirmation dialog [CHAR LIMIT=40] -->
+    <string name="retail_demo_reset_next">Next</string>
+    <!-- Title for carrier demo mode factory reset confirmation dialog. [CHAR LIMIT=40] -->
+    <string name="retail_demo_reset_title">Password required</string>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 337529a..221795b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1019,11 +1019,10 @@
     <!-- Monitoring dialog title for normal devices  [CHAR LIMIT=35]-->
     <string name="monitoring_title">Network monitoring</string>
 
-    <!-- STOPSHIP(b/33655277) Monitoring strings still need to be finalized and approved -->
-    <!-- Monitoring dialog subtitle for the section describing VPN [CHAR LIMIT=TODO]-->
+    <!-- Monitoring dialog subtitle for the section describing VPN [CHAR LIMIT=35]-->
     <string name="monitoring_subtitle_vpn">VPN</string>
 
-    <!-- Monitoring dialog subtitle for the section describing network logging [CHAR LIMIT=TODO]-->
+    <!-- Monitoring dialog subtitle for the section describing network logging [CHAR LIMIT=35]-->
     <string name="monitoring_subtitle_network_logging">Network Logging</string>
 
     <!-- Monitoring dialog disable vpn button [CHAR LIMIT=30] -->
@@ -1033,7 +1032,7 @@
     <string name="disconnect_vpn">Disconnect VPN</string>
 
     <!-- Monitoring dialog device owner body text [CHAR LIMIT=400] -->
-    <string name="monitoring_description_device_owned">Your device is managed by <xliff:g id="organization">%1$s</xliff:g>.\n\nYour administrator can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information. For more information, contact your administrator.</string>
+    <string name="monitoring_description_device_owned">Your device is managed by <xliff:g id="organization">%1$s</xliff:g>.\n\nYour admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information. For more information, contact your admin.</string>
 
     <!-- Monitoring dialog: Part of text body explaining that a VPN is connected and what it can do, for devices managed by a Device Owner app [CHAR LIMIT=130] -->
     <string name="monitoring_description_do_body_vpn">You\'re connected to <xliff:g id="vpn_app">%1$s</xliff:g>, which can monitor your network activity, including emails, apps, and websites.</string>
@@ -1041,21 +1040,21 @@
     <!-- Monitoring dialog: Space that separates the VPN body text and the "Open VPN Settings" link that follows it. [CHAR LIMIT=5] -->
     <string name="monitoring_description_vpn_settings_separator">" "</string>
 
-    <!-- Monitoring dialog: Link to open the VPN settings page [CHAR LIMIT=TODO] -->
+    <!-- Monitoring dialog: Link to open the VPN settings page [CHAR LIMIT=60] -->
     <string name="monitoring_description_vpn_settings">Open VPN Settings</string>
 
-    <!-- Monitoring dialog: Network logging text [CHAR LIMIT=TODO] -->
-    <string name="monitoring_description_network_logging">Your admin has turned on network logging, which monitors traffic on your device.\n\nFor more information contact your admin.</string>
+    <!-- Monitoring dialog: Network logging text [CHAR LIMIT=400] -->
+    <string name="monitoring_description_network_logging">Your admin has turned on network logging, which monitors traffic on your device.\n\nFor more information, contact your admin.</string>
 
 
     <!-- Monitoring dialog VPN text [CHAR LIMIT=400] -->
     <string name="monitoring_description_vpn">You gave an app permission to set up a VPN connection.\n\nThis app can monitor your device and network activity, including emails, apps, and websites.</string>
 
     <!-- Monitoring dialog VPN with device owner text [CHAR LIMIT=400] -->
-    <string name="monitoring_description_vpn_device_owned">Your device is managed by <xliff:g id="organization">%1$s</xliff:g>.\n\nYour administrator can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.\n\nYou\'re connected to a VPN, which can monitor your network activity, including emails, apps, and websites.\n\nFor more information, contact your administrator.</string>
+    <string name="monitoring_description_vpn_device_owned">Your device is managed by <xliff:g id="organization">%1$s</xliff:g>.\n\nYour admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.\n\nYou\'re connected to a VPN, which can monitor your network activity, including emails, apps, and websites.\n\nFor more information, contact your admin.</string>
 
     <!-- Monitoring dialog VPN with profile owner text [CHAR LIMIT=400] -->
-    <string name="monitoring_description_vpn_profile_owned">Your work profile is managed by <xliff:g id="organization">%1$s</xliff:g>.\n\nYour administrator is capable of monitoring your network activity including emails, apps, and websites.\n\nFor more information, contact your administrator.\n\nYou\'re also connected to a VPN, which can monitor your network activity.</string>
+    <string name="monitoring_description_vpn_profile_owned">Your work profile is managed by <xliff:g id="organization">%1$s</xliff:g>.\n\nYour admin is capable of monitoring your network activity including emails, apps, and websites.\n\nFor more information, contact your admin.\n\nYou\'re also connected to a VPN, which can monitor your network activity.</string>
 
     <!-- Name for a generic legacy VPN connection [CHAR LIMIT=20] -->
     <string name="legacy_vpn_name">VPN</string>
@@ -1070,13 +1069,13 @@
     <string name="branded_monitoring_description_app_personal">You\'re connected to <xliff:g id="application">%1$s</xliff:g>, which can monitor your personal network activity, including emails, apps, and websites.</string>
 
     <!-- Monitoring dialog text for single app (inside work profile) [CHAR LIMIT=400] -->
-    <string name="monitoring_description_app_work">Your work profile is managed by <xliff:g id="organization">%1$s</xliff:g>. It is connected to <xliff:g id="application">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps, and websites.\n\nFor more information, contact your administrator.</string>
+    <string name="monitoring_description_app_work">Your work profile is managed by <xliff:g id="organization">%1$s</xliff:g>. It is connected to <xliff:g id="application">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps, and websites.\n\nFor more information, contact your admin.</string>
 
     <!-- Monitoring dialog text for multiple apps (in personal and work profiles) [CHAR LIMIT=400] -->
     <string name="monitoring_description_app_personal_work">Your work profile is managed by <xliff:g id="organization">%1$s</xliff:g>. It is connected to <xliff:g id="application_work">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps, and websites.\n\nYou\'re also connected to <xliff:g id="application_personal">%3$s</xliff:g>, which can monitor your personal network activity.</string>
 
     <!-- Monitoring dialog text for single app (with device owner) [CHAR LIMIT=400] -->
-    <string name="monitoring_description_vpn_app_device_owned">Your device is managed by <xliff:g id="organization">%1$s</xliff:g>.\n\nYour administrator can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.\n\nYou\'re connected to <xliff:g id="application">%2$s</xliff:g>, which can monitor your network activity, including emails, apps, and websites.\n\nFor more information, contact your administrator.</string>
+    <string name="monitoring_description_vpn_app_device_owned">Your device is managed by <xliff:g id="organization">%1$s</xliff:g>.\n\nYour admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.\n\nYou\'re connected to <xliff:g id="application">%2$s</xliff:g>, which can monitor your network activity, including emails, apps, and websites.\n\nFor more information, contact your admin.</string>
 
     <!-- Indication on the keyguard that appears when the user disables trust agents until the next time they unlock manually. [CHAR LIMIT=NONE] -->
     <string name="keyguard_indication_trust_disabled">Device will stay locked until you manually unlock</string>
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index b3627ad..5549ced 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3130,10 +3130,7 @@
                 Settings.Global.TETHER_SUPPORTED, defaultVal) != 0)
                 && !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING);
         return tetherEnabledInSettings && mUserManager.isAdminUser() &&
-                ((mTethering.getTetherableUsbRegexs().length != 0 ||
-                mTethering.getTetherableWifiRegexs().length != 0 ||
-                mTethering.getTetherableBluetoothRegexs().length != 0) &&
-                mTethering.getUpstreamIfaceTypes().length != 0);
+               mTethering.hasTetherableConfiguration();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index 90f507c..40b0e2e 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -35,6 +35,8 @@
 
     private final Context mContext;
     private boolean mSafeMode;
+    private boolean mRuntimeRestarted;
+    private boolean mFirstBoot;
 
     // Services that should receive lifecycle events.
     private final ArrayList<SystemService> mServices = new ArrayList<SystemService>();
@@ -246,6 +248,28 @@
     }
 
     /**
+     * @return true if runtime was restarted, false if it's normal boot
+     */
+    public boolean isRuntimeRestarted() {
+        return mRuntimeRestarted;
+    }
+
+    void setRuntimeRestarted(boolean runtimeRestarted) {
+        mRuntimeRestarted = runtimeRestarted;
+    }
+
+    /**
+     * @return true if it's first boot after OTA
+     */
+    public boolean isFirstBoot() {
+        return mFirstBoot;
+    }
+
+    void setFirstBoot(boolean firstBoot) {
+        mFirstBoot = firstBoot;
+    }
+
+    /**
      * Outputs the state of this manager to the System log.
      */
     public void dump() {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 1ef3728..19eeff0 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -239,11 +239,12 @@
             // storage is already unlocked.
             if (uss.setState(STATE_BOOTING, STATE_RUNNING_LOCKED)) {
                 getUserManagerInternal().setUserState(userId, uss.state);
-
-                int uptimeSeconds = (int)(SystemClock.elapsedRealtime() / 1000);
-                MetricsLogger.histogram(mService.mContext, "framework_locked_boot_completed",
-                    uptimeSeconds);
-
+                if (!mService.mSystemServiceManager.isRuntimeRestarted()
+                        && !mService.mSystemServiceManager.isFirstBoot()) {
+                    int uptimeSeconds = (int)(SystemClock.elapsedRealtime() / 1000);
+                    MetricsLogger.histogram(mService.mContext, "framework_locked_boot_completed",
+                            uptimeSeconds);
+                }
                 Intent intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED, null);
                 intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
                 intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
@@ -415,8 +416,13 @@
             }
 
             Slog.d(TAG, "Sending BOOT_COMPLETE user #" + userId);
-            int uptimeSeconds = (int)(SystemClock.elapsedRealtime() / 1000);
-            MetricsLogger.histogram(mService.mContext, "framework_boot_completed", uptimeSeconds);
+            if (!mService.mSystemServiceManager.isRuntimeRestarted()
+                    && !mService.mSystemServiceManager.isFirstBoot()) {
+
+                int uptimeSeconds = (int) (SystemClock.elapsedRealtime() / 1000);
+                MetricsLogger
+                        .histogram(mService.mContext, "framework_boot_completed", uptimeSeconds);
+            }
             final Intent bootIntent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
             bootIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
             bootIntent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 0c80166..b82dd60 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -16,6 +16,11 @@
 
 package com.android.server.connectivity;
 
+import static android.hardware.usb.UsbManager.USB_CONNECTED;
+import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS;
+import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
+
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -108,11 +113,7 @@
     private static final SparseArray<String> sMagicDecoderRing =
             MessageUtils.findMessageNames(messageClasses);
 
-    // TODO - remove both of these - should be part of interface inspection/selection stuff
-    private String[] mTetherableUsbRegexs;
-    private String[] mTetherableWifiRegexs;
-    private String[] mTetherableBluetoothRegexs;
-    private Collection<Integer> mUpstreamIfaceTypes;
+    private volatile TetheringConfiguration mConfig;
 
     // used to synchronize public access to members
     private final Object mPublicSync;
@@ -121,10 +122,6 @@
     private static final Integer HIPRI_TYPE = new Integer(ConnectivityManager.TYPE_MOBILE_HIPRI);
     private static final Integer DUN_TYPE = new Integer(ConnectivityManager.TYPE_MOBILE_DUN);
 
-    // if we have to connect to mobile, what APN type should we use?  Calculated by examining the
-    // upstream type list and the DUN_REQUIRED secure-setting
-    private int mPreferredUpstreamMobileApn = ConnectivityManager.TYPE_NONE;
-
     private final INetworkManagementService mNMService;
     private final INetworkStatsService mStatsService;
     private final INetworkPolicyManager mPolicyManager;
@@ -150,24 +147,6 @@
     private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString(Resources
             .getSystem().getString(com.android.internal.R.string.config_wifi_tether_enable));
 
-    // USB is  192.168.42.1 and 255.255.255.0
-    // Wifi is 192.168.43.1 and 255.255.255.0
-    // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1
-    // with 255.255.255.0
-    // P2P is 192.168.49.1 and 255.255.255.0
-
-    private String[] mDhcpRange;
-    private static final String[] DHCP_DEFAULT_RANGE = {
-        "192.168.42.2", "192.168.42.254", "192.168.43.2", "192.168.43.254",
-        "192.168.44.2", "192.168.44.254", "192.168.45.2", "192.168.45.254",
-        "192.168.46.2", "192.168.46.254", "192.168.47.2", "192.168.47.254",
-        "192.168.48.2", "192.168.48.254", "192.168.49.2", "192.168.49.254",
-    };
-
-    private String[] mDefaultDnsServers;
-    private static final String DNS_DEFAULT_SERVER1 = "8.8.8.8";
-    private static final String DNS_DEFAULT_SERVER2 = "8.8.4.4";
-
     private final StateMachine mTetherMasterSM;
     private final UpstreamNetworkMonitor mUpstreamNetworkMonitor;
     private String mCurrentUpstreamIface;
@@ -208,27 +187,16 @@
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
         filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
         filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
-        mContext.registerReceiver(mStateReceiver, filter);
+        mContext.registerReceiver(mStateReceiver, filter, null, mTetherMasterSM.getHandler());
 
         filter = new IntentFilter();
         filter.addAction(Intent.ACTION_MEDIA_SHARED);
         filter.addAction(Intent.ACTION_MEDIA_UNSHARED);
         filter.addDataScheme("file");
-        mContext.registerReceiver(mStateReceiver, filter);
-
-        mDhcpRange = context.getResources().getStringArray(
-                com.android.internal.R.array.config_tether_dhcp_range);
-        if ((mDhcpRange.length == 0) || (mDhcpRange.length % 2 ==1)) {
-            mDhcpRange = DHCP_DEFAULT_RANGE;
-        }
+        mContext.registerReceiver(mStateReceiver, filter, null, mTetherMasterSM.getHandler());
 
         // load device config info
         updateConfiguration();
-
-        // TODO - remove and rely on real notifications of the current iface
-        mDefaultDnsServers = new String[2];
-        mDefaultDnsServers[0] = DNS_DEFAULT_SERVER1;
-        mDefaultDnsServers[1] = DNS_DEFAULT_SERVER2;
     }
 
     // We can't do this once in the Tethering() constructor and cache the value, because the
@@ -237,30 +205,8 @@
         return (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
     }
 
-    void updateConfiguration() {
-        String[] tetherableUsbRegexs = mContext.getResources().getStringArray(
-                com.android.internal.R.array.config_tether_usb_regexs);
-        String[] tetherableWifiRegexs = mContext.getResources().getStringArray(
-                com.android.internal.R.array.config_tether_wifi_regexs);
-        String[] tetherableBluetoothRegexs = mContext.getResources().getStringArray(
-                com.android.internal.R.array.config_tether_bluetooth_regexs);
-
-        int ifaceTypes[] = mContext.getResources().getIntArray(
-                com.android.internal.R.array.config_tether_upstream_types);
-        Collection<Integer> upstreamIfaceTypes = new ArrayList<>();
-        for (int i : ifaceTypes) {
-            upstreamIfaceTypes.add(new Integer(i));
-        }
-
-        synchronized (mPublicSync) {
-            mTetherableUsbRegexs = tetherableUsbRegexs;
-            mTetherableWifiRegexs = tetherableWifiRegexs;
-            mTetherableBluetoothRegexs = tetherableBluetoothRegexs;
-            mUpstreamIfaceTypes = upstreamIfaceTypes;
-        }
-
-        // check if the upstream type list needs to be modified due to secure-settings
-        checkDunRequired();
+    private void updateConfiguration() {
+        mConfig = new TetheringConfiguration(mContext);
     }
 
     @Override
@@ -300,39 +246,14 @@
         interfaceStatusChanged(iface, up);
     }
 
-    private boolean isUsb(String iface) {
-        synchronized (mPublicSync) {
-            for (String regex : mTetherableUsbRegexs) {
-                if (iface.matches(regex)) return true;
-            }
-            return false;
-        }
-    }
-
-    private boolean isWifi(String iface) {
-        synchronized (mPublicSync) {
-            for (String regex : mTetherableWifiRegexs) {
-                if (iface.matches(regex)) return true;
-            }
-            return false;
-        }
-    }
-
-    private boolean isBluetooth(String iface) {
-        synchronized (mPublicSync) {
-            for (String regex : mTetherableBluetoothRegexs) {
-                if (iface.matches(regex)) return true;
-            }
-            return false;
-        }
-    }
-
     private int ifaceNameToType(String iface) {
-        if (isWifi(iface)) {
+        final TetheringConfiguration cfg = mConfig;
+
+        if (cfg.isWifi(iface)) {
             return ConnectivityManager.TETHERING_WIFI;
-        } else if (isUsb(iface)) {
+        } else if (cfg.isUsb(iface)) {
             return ConnectivityManager.TETHERING_USB;
-        } else if (isBluetooth(iface)) {
+        } else if (cfg.isBluetooth(iface)) {
             return ConnectivityManager.TETHERING_BLUETOOTH;
         }
         return ConnectivityManager.TETHERING_INVALID;
@@ -662,6 +583,8 @@
         boolean usbTethered = false;
         boolean bluetoothTethered = false;
 
+        final TetheringConfiguration cfg = mConfig;
+
         synchronized (mPublicSync) {
             for (int i = 0; i < mTetherStates.size(); i++) {
                 TetherState tetherState = mTetherStates.valueAt(i);
@@ -671,11 +594,11 @@
                 } else if (tetherState.mLastState == IControlsTethering.STATE_AVAILABLE) {
                     availableList.add(iface);
                 } else if (tetherState.mLastState == IControlsTethering.STATE_TETHERED) {
-                    if (isUsb(iface)) {
+                    if (cfg.isUsb(iface)) {
                         usbTethered = true;
-                    } else if (isWifi(iface)) {
+                    } else if (cfg.isWifi(iface)) {
                         wifiTethered = true;
-                    } else if (isBluetooth(iface)) {
+                    } else if (cfg.isBluetooth(iface)) {
                         bluetoothTethered = true;
                     }
                     activeList.add(iface);
@@ -779,69 +702,84 @@
     private class StateReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context content, Intent intent) {
-            String action = intent.getAction();
-            if (action == null) { return; }
+            final String action = intent.getAction();
+            if (action == null) return;
+
             if (action.equals(UsbManager.ACTION_USB_STATE)) {
-                synchronized (Tethering.this.mPublicSync) {
-                    boolean usbConnected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false);
-                    mRndisEnabled = intent.getBooleanExtra(UsbManager.USB_FUNCTION_RNDIS, false);
-                    // start tethering if we have a request pending
-                    if (usbConnected && mRndisEnabled && mUsbTetherRequested) {
-                        tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_USB);
-                    }
-                    mUsbTetherRequested = false;
-                }
+                handleUsbAction(intent);
             } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
-                NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra(
-                        ConnectivityManager.EXTRA_NETWORK_INFO);
-                if (networkInfo != null &&
-                        networkInfo.getDetailedState() != NetworkInfo.DetailedState.FAILED) {
-                    if (VDBG) Log.d(TAG, "Tethering got CONNECTIVITY_ACTION");
-                    mTetherMasterSM.sendMessage(TetherMasterSM.CMD_UPSTREAM_CHANGED);
-                }
+                handleConnectivityAction(intent);
             } else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) {
-                synchronized (Tethering.this.mPublicSync) {
-                    int curState =  intent.getIntExtra(WifiManager.EXTRA_WIFI_AP_STATE,
-                            WifiManager.WIFI_AP_STATE_DISABLED);
-                    switch (curState) {
-                        case WifiManager.WIFI_AP_STATE_ENABLING:
-                            // We can see this state on the way to both enabled and failure states.
-                            break;
-                        case WifiManager.WIFI_AP_STATE_ENABLED:
-                            // When the AP comes up and we've been requested to tether it, do so.
-                            if (mWifiTetherRequested) {
-                                tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_WIFI);
-                            }
-                            break;
-                        case WifiManager.WIFI_AP_STATE_DISABLED:
-                        case WifiManager.WIFI_AP_STATE_DISABLING:
-                        case WifiManager.WIFI_AP_STATE_FAILED:
-                        default:
-                            if (DBG) {
-                                Log.d(TAG, "Canceling WiFi tethering request - AP_STATE=" +
-                                    curState);
-                            }
-                            // Tell appropriate interface state machines that they should tear
-                            // themselves down.
-                            for (int i = 0; i < mTetherStates.size(); i++) {
-                                TetherInterfaceStateMachine tism =
-                                        mTetherStates.valueAt(i).mStateMachine;
-                                if (tism.interfaceType() == ConnectivityManager.TETHERING_WIFI) {
-                                    tism.sendMessage(
-                                            TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
-                                    break;  // There should be at most one of these.
-                                }
-                            }
-                            // Regardless of whether we requested this transition, the AP has gone
-                            // down.  Don't try to tether again unless we're requested to do so.
-                            mWifiTetherRequested = false;
-                            break;
-                    }
-                }
+                handleWifiApAction(intent);
             } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
                 updateConfiguration();
             }
         }
+
+        private void handleConnectivityAction(Intent intent) {
+            final NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra(
+                    ConnectivityManager.EXTRA_NETWORK_INFO);
+            if (networkInfo == null ||
+                    networkInfo.getDetailedState() == NetworkInfo.DetailedState.FAILED) {
+                return;
+            }
+
+            if (VDBG) Log.d(TAG, "Tethering got CONNECTIVITY_ACTION: " + networkInfo.toString());
+            mTetherMasterSM.sendMessage(TetherMasterSM.CMD_UPSTREAM_CHANGED);
+        }
+
+        private void handleUsbAction(Intent intent) {
+            final boolean usbConnected = intent.getBooleanExtra(USB_CONNECTED, false);
+            final boolean rndisEnabled = intent.getBooleanExtra(USB_FUNCTION_RNDIS, false);
+            synchronized (Tethering.this.mPublicSync) {
+                mRndisEnabled = rndisEnabled;
+                // start tethering if we have a request pending
+                if (usbConnected && mRndisEnabled && mUsbTetherRequested) {
+                    tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_USB);
+                }
+                mUsbTetherRequested = false;
+            }
+        }
+
+        private void handleWifiApAction(Intent intent) {
+            final int curState =  intent.getIntExtra(EXTRA_WIFI_AP_STATE, WIFI_AP_STATE_DISABLED);
+            synchronized (Tethering.this.mPublicSync) {
+                switch (curState) {
+                    case WifiManager.WIFI_AP_STATE_ENABLING:
+                        // We can see this state on the way to both enabled and failure states.
+                        break;
+                    case WifiManager.WIFI_AP_STATE_ENABLED:
+                        // When the AP comes up and we've been requested to tether it, do so.
+                        if (mWifiTetherRequested) {
+                            tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_WIFI);
+                        }
+                        break;
+                    case WifiManager.WIFI_AP_STATE_DISABLED:
+                    case WifiManager.WIFI_AP_STATE_DISABLING:
+                    case WifiManager.WIFI_AP_STATE_FAILED:
+                    default:
+                        if (DBG) {
+                            Log.d(TAG, "Canceling WiFi tethering request - AP_STATE=" +
+                                curState);
+                        }
+                        // Tell appropriate interface state machines that they should tear
+                        // themselves down.
+                        for (int i = 0; i < mTetherStates.size(); i++) {
+                            TetherInterfaceStateMachine tism =
+                                    mTetherStates.valueAt(i).mStateMachine;
+                            if (tism.interfaceType() == ConnectivityManager.TETHERING_WIFI) {
+                                tism.sendMessage(
+                                        TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
+                                break;  // There should be at most one of these.
+                            }
+                        }
+                        // Regardless of whether we requested this transition, the AP has gone
+                        // down.  Don't try to tether again unless we're requested to do so.
+                        mWifiTetherRequested = false;
+                    break;
+                }
+            }
+        }
     }
 
     private void tetherMatchingInterfaces(boolean enable, int interfaceType) {
@@ -875,26 +813,38 @@
         }
     }
 
-    // TODO - return copies so people can't tamper
+    public TetheringConfiguration getTetheringConfiguration() {
+        return mConfig;
+    }
+
+    public boolean hasTetherableConfiguration() {
+        final TetheringConfiguration cfg = mConfig;
+        final boolean hasDownstreamConfiguration =
+                (cfg.tetherableUsbRegexs.length != 0) ||
+                (cfg.tetherableWifiRegexs.length != 0) ||
+                (cfg.tetherableBluetoothRegexs.length != 0);
+        final boolean hasUpstreamConfiguration = !cfg.preferredUpstreamIfaceTypes.isEmpty();
+
+        return hasDownstreamConfiguration && hasUpstreamConfiguration;
+    }
+
+    // TODO - update callers to use getTetheringConfiguration(),
+    // which has only final members.
     public String[] getTetherableUsbRegexs() {
-        return mTetherableUsbRegexs;
+        return copy(mConfig.tetherableUsbRegexs);
     }
 
     public String[] getTetherableWifiRegexs() {
-        return mTetherableWifiRegexs;
+        return copy(mConfig.tetherableWifiRegexs);
     }
 
     public String[] getTetherableBluetoothRegexs() {
-        return mTetherableBluetoothRegexs;
+        return copy(mConfig.tetherableBluetoothRegexs);
     }
 
     public int setUsbTethering(boolean enable) {
         if (VDBG) Log.d(TAG, "setUsbTethering(" + enable + ")");
         UsbManager usbManager = mContext.getSystemService(UsbManager.class);
-        if (usbManager == null) {
-            return enable ? ConnectivityManager.TETHER_ERROR_MASTER_ERROR
-                          : ConnectivityManager.TETHER_ERROR_NO_ERROR;
-        }
 
         synchronized (mPublicSync) {
             if (enable) {
@@ -925,61 +875,6 @@
         return ConnectivityManager.TETHER_ERROR_NO_ERROR;
     }
 
-    public int[] getUpstreamIfaceTypes() {
-        int values[];
-        synchronized (mPublicSync) {
-            updateConfiguration();  // TODO - remove?
-            values = new int[mUpstreamIfaceTypes.size()];
-            Iterator<Integer> iterator = mUpstreamIfaceTypes.iterator();
-            for (int i=0; i < mUpstreamIfaceTypes.size(); i++) {
-                values[i] = iterator.next();
-            }
-        }
-        return values;
-    }
-
-    private void checkDunRequired() {
-        int secureSetting = 2;
-        TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
-        if (tm != null) {
-            secureSetting = tm.getTetherApnRequired();
-        }
-        synchronized (mPublicSync) {
-            // 2 = not set, 0 = DUN not required, 1 = DUN required
-            if (secureSetting != 2) {
-                int requiredApn = (secureSetting == 1 ?
-                        ConnectivityManager.TYPE_MOBILE_DUN :
-                        ConnectivityManager.TYPE_MOBILE_HIPRI);
-                if (requiredApn == ConnectivityManager.TYPE_MOBILE_DUN) {
-                    while (mUpstreamIfaceTypes.contains(MOBILE_TYPE)) {
-                        mUpstreamIfaceTypes.remove(MOBILE_TYPE);
-                    }
-                    while (mUpstreamIfaceTypes.contains(HIPRI_TYPE)) {
-                        mUpstreamIfaceTypes.remove(HIPRI_TYPE);
-                    }
-                    if (mUpstreamIfaceTypes.contains(DUN_TYPE) == false) {
-                        mUpstreamIfaceTypes.add(DUN_TYPE);
-                    }
-                } else {
-                    while (mUpstreamIfaceTypes.contains(DUN_TYPE)) {
-                        mUpstreamIfaceTypes.remove(DUN_TYPE);
-                    }
-                    if (mUpstreamIfaceTypes.contains(MOBILE_TYPE) == false) {
-                        mUpstreamIfaceTypes.add(MOBILE_TYPE);
-                    }
-                    if (mUpstreamIfaceTypes.contains(HIPRI_TYPE) == false) {
-                        mUpstreamIfaceTypes.add(HIPRI_TYPE);
-                    }
-                }
-            }
-            if (mUpstreamIfaceTypes.contains(DUN_TYPE)) {
-                mPreferredUpstreamMobileApn = ConnectivityManager.TYPE_MOBILE_DUN;
-            } else {
-                mPreferredUpstreamMobileApn = ConnectivityManager.TYPE_MOBILE_HIPRI;
-            }
-        }
-    }
-
     // TODO review API - maybe return ArrayList<String> here and below?
     public String[] getTetheredIfaces() {
         ArrayList<String> list = new ArrayList<String>();
@@ -1008,7 +903,7 @@
     }
 
     public String[] getTetheredDhcpRanges() {
-        return mDhcpRange;
+        return mConfig.dhcpRanges;
     }
 
     public String[] getErroredIfaces() {
@@ -1083,7 +978,7 @@
         private final ArrayList<TetherInterfaceStateMachine> mNotifyList;
         private final IPv6TetheringCoordinator mIPv6TetheringCoordinator;
 
-        private int mPreviousMobileApn = ConnectivityManager.TYPE_NONE;
+        private int mPreviousMobileType = ConnectivityManager.TYPE_NONE;
 
         private static final int UPSTREAM_SETTLE_TIME_MS     = 10000;
 
@@ -1118,13 +1013,13 @@
                 return false;
             }
 
-            protected boolean turnOnUpstreamMobileConnection(int apnType) {
+            protected boolean requestUpstreamMobileConnection(int apnType) {
                 if (apnType == ConnectivityManager.TYPE_NONE) { return false; }
 
-                if (apnType != mPreviousMobileApn) {
+                if (apnType != mPreviousMobileType) {
                     // Unregister any previous mobile upstream callback because
                     // this request, if any, will be different.
-                    turnOffUpstreamMobileConnection();
+                    unrequestUpstreamMobileConnection();
                 }
 
                 if (mUpstreamNetworkMonitor.mobileNetworkRequested()) {
@@ -1136,28 +1031,29 @@
                     case ConnectivityManager.TYPE_MOBILE_DUN:
                     case ConnectivityManager.TYPE_MOBILE:
                     case ConnectivityManager.TYPE_MOBILE_HIPRI:
-                        mPreviousMobileApn = apnType;
+                        mPreviousMobileType = apnType;
                         break;
                     default:
                         return false;
                 }
 
-                // TODO: This should be called by the code that observes
-                // configuration changes, once the above code in this function
-                // is simplified (i.e. eradicated).
-                mUpstreamNetworkMonitor.mobileUpstreamRequiresDun(
+                // TODO: Replace this with a call to pass the current tethering
+                // configuration to mUpstreamNetworkMonitor and let it handle
+                // choosing APN type accordingly.
+                mUpstreamNetworkMonitor.updateMobileRequiresDun(
                         apnType == ConnectivityManager.TYPE_MOBILE_DUN);
 
                 mUpstreamNetworkMonitor.registerMobileNetworkRequest();
                 return true;
             }
 
-            protected void turnOffUpstreamMobileConnection() {
+            protected void unrequestUpstreamMobileConnection() {
                 mUpstreamNetworkMonitor.releaseMobileNetworkRequest();
-                mPreviousMobileApn = ConnectivityManager.TYPE_NONE;
+                mPreviousMobileType = ConnectivityManager.TYPE_NONE;
             }
 
             protected boolean turnOnMasterTetherSettings() {
+                final TetheringConfiguration cfg = mConfig;
                 try {
                     mNMService.setIpForwardingEnabled(true);
                 } catch (Exception e) {
@@ -1165,11 +1061,11 @@
                     return false;
                 }
                 try {
-                    mNMService.startTethering(mDhcpRange);
+                    mNMService.startTethering(cfg.dhcpRanges);
                 } catch (Exception e) {
                     try {
                         mNMService.stopTethering();
-                        mNMService.startTethering(mDhcpRange);
+                        mNMService.startTethering(cfg.dhcpRanges);
                     } catch (Exception ee) {
                         transitionTo(mStartTetheringErrorState);
                         return false;
@@ -1202,29 +1098,31 @@
 
                 updateConfiguration(); // TODO - remove?
 
-                synchronized (mPublicSync) {
-                    if (VDBG) {
-                        Log.d(TAG, "chooseUpstreamType has upstream iface types:");
-                        for (Integer netType : mUpstreamIfaceTypes) {
-                            Log.d(TAG, " " + netType);
-                        }
-                    }
-
-                    for (Integer netType : mUpstreamIfaceTypes) {
-                        NetworkInfo info = cm.getNetworkInfo(netType.intValue());
-                        // TODO: if the network is suspended we should consider
-                        // that to be the same as connected here.
-                        if ((info != null) && info.isConnected()) {
-                            upType = netType.intValue();
-                            break;
-                        }
+                final TetheringConfiguration cfg = mConfig;
+                if (VDBG) {
+                    Log.d(TAG, "chooseUpstreamType has upstream iface types:");
+                    for (Integer netType : cfg.preferredUpstreamIfaceTypes) {
+                        Log.d(TAG, " " + netType);
                     }
                 }
 
+                for (Integer netType : cfg.preferredUpstreamIfaceTypes) {
+                    NetworkInfo info = cm.getNetworkInfo(netType.intValue());
+                    // TODO: if the network is suspended we should consider
+                    // that to be the same as connected here.
+                    if ((info != null) && info.isConnected()) {
+                        upType = netType.intValue();
+                        break;
+                    }
+                }
+
+                final int preferredUpstreamMobileApn = cfg.isDunRequired
+                        ? ConnectivityManager.TYPE_MOBILE_DUN
+                        : ConnectivityManager.TYPE_MOBILE_HIPRI;
                 if (DBG) {
                     Log.d(TAG, "chooseUpstreamType(" + tryCell + "),"
                             + " preferredApn="
-                            + ConnectivityManager.getNetworkTypeName(mPreferredUpstreamMobileApn)
+                            + ConnectivityManager.getNetworkTypeName(preferredUpstreamMobileApn)
                             + ", got type="
                             + ConnectivityManager.getNetworkTypeName(upType));
                 }
@@ -1233,11 +1131,11 @@
                     case ConnectivityManager.TYPE_MOBILE_DUN:
                     case ConnectivityManager.TYPE_MOBILE_HIPRI:
                         // If we're on DUN, put our own grab on it.
-                        turnOnUpstreamMobileConnection(upType);
+                        requestUpstreamMobileConnection(upType);
                         break;
                     case ConnectivityManager.TYPE_NONE:
                         if (tryCell &&
-                                turnOnUpstreamMobileConnection(mPreferredUpstreamMobileApn)) {
+                                requestUpstreamMobileConnection(preferredUpstreamMobileApn)) {
                             // We think mobile should be coming up; don't set a retry.
                         } else {
                             sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
@@ -1250,7 +1148,7 @@
                          * If we found NONE we don't want to do this as we want any previous
                          * requests to keep trying to bring up something we can use.
                          */
-                        turnOffUpstreamMobileConnection();
+                        unrequestUpstreamMobileConnection();
                         break;
                 }
 
@@ -1295,7 +1193,8 @@
             }
 
             protected void setDnsForwarders(final Network network, final LinkProperties lp) {
-                String[] dnsServers = mDefaultDnsServers;
+                // TODO: Set v4 and/or v6 DNS per available connectivity.
+                String[] dnsServers = mConfig.defaultIPv4DNS;
                 final Collection<InetAddress> dnses = lp.getDnsServers();
                 // TODO: Properly support the absence of DNS servers.
                 if (dnses != null && !dnses.isEmpty()) {
@@ -1330,96 +1229,127 @@
             }
         }
 
-        private final AtomicInteger mSimBcastGenerationNumber = new AtomicInteger(0);
-        private SimChangeBroadcastReceiver mBroadcastReceiver = null;
+        private class SimChangeListener {
+            private final Context mContext;
+            private final AtomicInteger mSimBcastGenerationNumber;
+            private BroadcastReceiver mBroadcastReceiver;
 
-        private void startListeningForSimChanges() {
-            if (DBG) Log.d(TAG, "startListeningForSimChanges");
-            if (mBroadcastReceiver == null) {
+            SimChangeListener(Context ctx) {
+                mContext = ctx;
+                mSimBcastGenerationNumber = new AtomicInteger(0);
+            }
+
+            public int generationNumber() {
+                return mSimBcastGenerationNumber.get();
+            }
+
+            public void startListening() {
+                if (DBG) Log.d(TAG, "startListening for SIM changes");
+
+                if (mBroadcastReceiver != null) return;
+
                 mBroadcastReceiver = new SimChangeBroadcastReceiver(
                         mSimBcastGenerationNumber.incrementAndGet());
                 final IntentFilter filter = new IntentFilter();
                 filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
 
-                mContext.registerReceiver(mBroadcastReceiver, filter);
+                mContext.registerReceiver(mBroadcastReceiver, filter, null,
+                        mTetherMasterSM.getHandler());
             }
-        }
 
-        private void stopListeningForSimChanges() {
-            if (DBG) Log.d(TAG, "stopListeningForSimChanges");
-            if (mBroadcastReceiver != null) {
+            public void stopListening() {
+                if (DBG) Log.d(TAG, "stopListening for SIM changes");
+
+                if (mBroadcastReceiver == null) return;
+
                 mSimBcastGenerationNumber.incrementAndGet();
                 mContext.unregisterReceiver(mBroadcastReceiver);
                 mBroadcastReceiver = null;
             }
-        }
 
-        class SimChangeBroadcastReceiver extends BroadcastReceiver {
-            // used to verify this receiver is still current
-            final private int mGenerationNumber;
-
-            // we're interested in edge-triggered LOADED notifications, so
-            // ignore LOADED unless we saw an ABSENT state first
-            private boolean mSimAbsentSeen = false;
-
-            public SimChangeBroadcastReceiver(int generationNumber) {
-                super();
-                mGenerationNumber = generationNumber;
+            public boolean hasMobileHotspotProvisionApp() {
+                try {
+                    if (!mContext.getResources().getString(com.android.internal.R.string.
+                            config_mobile_hotspot_provision_app_no_ui).isEmpty()) {
+                        Log.d(TAG, "re-evaluate provisioning");
+                        return true;
+                    }
+                } catch (Resources.NotFoundException e) {}
+                Log.d(TAG, "no prov-check needed for new SIM");
+                return false;
             }
 
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                if (DBG) {
-                    Log.d(TAG, "simchange mGenerationNumber=" + mGenerationNumber +
-                            ", current generationNumber=" + mSimBcastGenerationNumber.get());
-                }
-                if (mGenerationNumber != mSimBcastGenerationNumber.get()) return;
+            private boolean isSimCardAbsent(String state) {
+                return IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(state);
+            }
 
-                final String state =
-                        intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE);
+            private boolean isSimCardLoaded(String state) {
+                return IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(state);
+            }
 
-                Log.d(TAG, "got Sim changed to state " + state + ", mSimAbsentSeen=" +
-                        mSimAbsentSeen);
-                if (!mSimAbsentSeen && IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(state)) {
-                    mSimAbsentSeen = true;
+            private void startProvisionIntent(int tetherType) {
+                final Intent startProvIntent = new Intent();
+                startProvIntent.putExtra(ConnectivityManager.EXTRA_ADD_TETHER_TYPE, tetherType);
+                startProvIntent.putExtra(ConnectivityManager.EXTRA_RUN_PROVISION, true);
+                startProvIntent.setComponent(TETHER_SERVICE);
+                mContext.startServiceAsUser(startProvIntent, UserHandle.CURRENT);
+            }
+
+            private class SimChangeBroadcastReceiver extends BroadcastReceiver {
+                // used to verify this receiver is still current
+                final private int mGenerationNumber;
+
+                // we're interested in edge-triggered LOADED notifications, so
+                // ignore LOADED unless we saw an ABSENT state first
+                private boolean mSimAbsentSeen = false;
+
+                public SimChangeBroadcastReceiver(int generationNumber) {
+                    mGenerationNumber = generationNumber;
                 }
 
-                if (mSimAbsentSeen && IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(state)) {
-                    mSimAbsentSeen = false;
-                    try {
-                        if (mContext.getResources().getString(com.android.internal.R.string.
-                                config_mobile_hotspot_provision_app_no_ui).isEmpty() == false) {
-                            ArrayList<Integer> tethered = new ArrayList<Integer>();
-                            synchronized (mPublicSync) {
-                                for (int i = 0; i < mTetherStates.size(); i++) {
-                                    TetherState tetherState = mTetherStates.valueAt(i);
-                                    if (tetherState.mLastState !=
-                                            IControlsTethering.STATE_TETHERED) {
-                                        continue;  // Skip interfaces that aren't tethered.
-                                    }
-                                    String iface = mTetherStates.keyAt(i);
-                                    int interfaceType = ifaceNameToType(iface);
-                                    if (interfaceType != ConnectivityManager.TETHERING_INVALID) {
-                                        tethered.add(new Integer(interfaceType));
-                                    }
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    final int currentGenerationNumber = mSimBcastGenerationNumber.get();
+
+                    if (DBG) {
+                        Log.d(TAG, "simchange mGenerationNumber=" + mGenerationNumber +
+                                ", current generationNumber=" + currentGenerationNumber);
+                    }
+                    if (mGenerationNumber != currentGenerationNumber) return;
+
+                    final String state = intent.getStringExtra(
+                            IccCardConstants.INTENT_KEY_ICC_STATE);
+                    Log.d(TAG, "got Sim changed to state " + state + ", mSimAbsentSeen=" +
+                            mSimAbsentSeen);
+
+                    if (isSimCardAbsent(state)) {
+                        if (!mSimAbsentSeen) mSimAbsentSeen = true;
+                        return;
+                    }
+
+                    if (isSimCardLoaded(state) && mSimAbsentSeen) {
+                        mSimAbsentSeen = false;
+
+                        if (!hasMobileHotspotProvisionApp()) return;
+
+                        ArrayList<Integer> tethered = new ArrayList<Integer>();
+                        synchronized (mPublicSync) {
+                            for (int i = 0; i < mTetherStates.size(); i++) {
+                                TetherState tetherState = mTetherStates.valueAt(i);
+                                if (tetherState.mLastState != IControlsTethering.STATE_TETHERED) {
+                                    continue;  // Skip interfaces that aren't tethered.
+                                }
+                                String iface = mTetherStates.keyAt(i);
+                                int interfaceType = ifaceNameToType(iface);
+                                if (interfaceType != ConnectivityManager.TETHERING_INVALID) {
+                                    tethered.add(new Integer(interfaceType));
                                 }
                             }
-                            for (int tetherType : tethered) {
-                                Intent startProvIntent = new Intent();
-                                startProvIntent.putExtra(
-                                        ConnectivityManager.EXTRA_ADD_TETHER_TYPE, tetherType);
-                                startProvIntent.putExtra(
-                                        ConnectivityManager.EXTRA_RUN_PROVISION, true);
-                                startProvIntent.setComponent(TETHER_SERVICE);
-                                mContext.startServiceAsUser(startProvIntent, UserHandle.CURRENT);
-                            }
-                            Log.d(TAG, "re-evaluate provisioning");
-                        } else {
-                            Log.d(TAG, "no prov-check needed for new SIM");
                         }
-                    } catch (Resources.NotFoundException e) {
-                        Log.d(TAG, "no prov-check needed for new SIM");
-                        // not defined, do nothing
+
+                        for (int tetherType : tethered) {
+                            startProvisionIntent(tetherType);
+                        }
                     }
                 }
             }
@@ -1455,12 +1385,13 @@
         }
 
         class TetherModeAliveState extends TetherMasterUtilState {
+            final SimChangeListener simChange = new SimChangeListener(mContext);
             boolean mTryCell = true;
             @Override
             public void enter() {
                 // TODO: examine if we should check the return value.
                 turnOnMasterTetherSettings(); // may transition us out
-                startListeningForSimChanges();
+                simChange.startListening();
                 mUpstreamNetworkMonitor.start();
 
                 mTryCell = true;  // better try something first pass or crazy tests cases will fail
@@ -1470,9 +1401,9 @@
 
             @Override
             public void exit() {
-                turnOffUpstreamMobileConnection();
+                unrequestUpstreamMobileConnection();
                 mUpstreamNetworkMonitor.stop();
-                stopListeningForSimChanges();
+                simChange.stopListening();
                 notifyTetheredOfNewUpstreamIface(null);
                 handleNewUpstreamNetworkState(null);
             }
@@ -1672,9 +1603,10 @@
 
         pw.println("Tethering:");
         pw.increaseIndent();
-        pw.print("mUpstreamIfaceTypes:");
+        final TetheringConfiguration cfg = mConfig;
+        pw.print("preferredUpstreamIfaceTypes:");
         synchronized (mPublicSync) {
-            for (Integer netType : mUpstreamIfaceTypes) {
+            for (Integer netType : cfg.preferredUpstreamIfaceTypes) {
                 pw.print(" " + ConnectivityManager.getNetworkTypeName(netType));
             }
             pw.println();
@@ -1754,4 +1686,123 @@
         mTetherStates.put(iface, tetherState);
         tetherState.mStateMachine.start();
     }
+
+    private static String[] copy(String[] strarray) {
+        return Arrays.copyOf(strarray, strarray.length);
+    }
+
+    private static class TetheringConfiguration {
+        private static final int DUN_NOT_REQUIRED = 0;
+        private static final int DUN_REQUIRED = 1;
+        private static final int DUN_UNSPECIFIED = 2;
+
+        // USB is  192.168.42.1 and 255.255.255.0
+        // Wifi is 192.168.43.1 and 255.255.255.0
+        // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1
+        // with 255.255.255.0
+        // P2P is 192.168.49.1 and 255.255.255.0
+        private static final String[] DHCP_DEFAULT_RANGE = {
+            "192.168.42.2", "192.168.42.254", "192.168.43.2", "192.168.43.254",
+            "192.168.44.2", "192.168.44.254", "192.168.45.2", "192.168.45.254",
+            "192.168.46.2", "192.168.46.254", "192.168.47.2", "192.168.47.254",
+            "192.168.48.2", "192.168.48.254", "192.168.49.2", "192.168.49.254",
+        };
+
+        private final String[] DEFAULT_IPV4_DNS = {"8.8.4.4", "8.8.8.8"};
+
+        public final String[] tetherableUsbRegexs;
+        public final String[] tetherableWifiRegexs;
+        public final String[] tetherableBluetoothRegexs;
+        public final boolean isDunRequired;
+        public final Collection<Integer> preferredUpstreamIfaceTypes;
+        public final String[] dhcpRanges;
+        public final String[] defaultIPv4DNS;
+
+        public TetheringConfiguration(Context ctx) {
+            tetherableUsbRegexs = ctx.getResources().getStringArray(
+                    com.android.internal.R.array.config_tether_usb_regexs);
+            tetherableWifiRegexs = ctx.getResources().getStringArray(
+                    com.android.internal.R.array.config_tether_wifi_regexs);
+            tetherableBluetoothRegexs = ctx.getResources().getStringArray(
+                    com.android.internal.R.array.config_tether_bluetooth_regexs);
+
+            isDunRequired = checkDunRequired(ctx);
+            preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(ctx, isDunRequired);
+
+            dhcpRanges = getDhcpRanges(ctx);
+            defaultIPv4DNS = copy(DEFAULT_IPV4_DNS);
+        }
+
+        public boolean isUsb(String iface) {
+            for (String regex : tetherableUsbRegexs) {
+                if (iface.matches(regex)) return true;
+            }
+            return false;
+        }
+
+        public boolean isWifi(String iface) {
+            for (String regex : tetherableWifiRegexs) {
+                if (iface.matches(regex)) return true;
+            }
+            return false;
+        }
+
+        public boolean isBluetooth(String iface) {
+            for (String regex : tetherableBluetoothRegexs) {
+                if (iface.matches(regex)) return true;
+            }
+            return false;
+        }
+
+        private static boolean checkDunRequired(Context ctx) {
+            final TelephonyManager tm = ctx.getSystemService(TelephonyManager.class);
+            final int secureSetting =
+                    (tm != null) ? tm.getTetherApnRequired() : DUN_UNSPECIFIED;
+            return (secureSetting == DUN_REQUIRED);
+        }
+
+        private static Collection<Integer> getUpstreamIfaceTypes(Context ctx, boolean requiresDun) {
+            final int ifaceTypes[] = ctx.getResources().getIntArray(
+                    com.android.internal.R.array.config_tether_upstream_types);
+            final ArrayList<Integer> upstreamIfaceTypes = new ArrayList<>(ifaceTypes.length);
+            for (int i : ifaceTypes) {
+                upstreamIfaceTypes.add(i);
+            }
+
+            // Fix up upstream interface types for DUN or mobile.
+            // TODO: Perform this filtering step in the above for loop.
+            if (requiresDun) {
+                while (upstreamIfaceTypes.contains(MOBILE_TYPE)) {
+                    upstreamIfaceTypes.remove(MOBILE_TYPE);
+                }
+                while (upstreamIfaceTypes.contains(HIPRI_TYPE)) {
+                    upstreamIfaceTypes.remove(HIPRI_TYPE);
+                }
+                if (!upstreamIfaceTypes.contains(DUN_TYPE)) {
+                    upstreamIfaceTypes.add(DUN_TYPE);
+                }
+            } else {
+                while (upstreamIfaceTypes.contains(DUN_TYPE)) {
+                    upstreamIfaceTypes.remove(DUN_TYPE);
+                }
+                if (!upstreamIfaceTypes.contains(MOBILE_TYPE)) {
+                    upstreamIfaceTypes.add(MOBILE_TYPE);
+                }
+                if (!upstreamIfaceTypes.contains(HIPRI_TYPE)) {
+                    upstreamIfaceTypes.add(HIPRI_TYPE);
+                }
+            }
+
+            return upstreamIfaceTypes;
+        }
+
+        private static String[] getDhcpRanges(Context ctx) {
+            final String[] fromResource = ctx.getResources().getStringArray(
+                    com.android.internal.R.array.config_tether_dhcp_range);
+            if ((fromResource.length > 0) && (fromResource.length % 2 == 0)) {
+                return fromResource;
+            }
+            return copy(DHCP_DEFAULT_RANGE);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index 4c950de..23481dc 100644
--- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -109,7 +109,7 @@
         mNetworkMap.clear();
     }
 
-    public void mobileUpstreamRequiresDun(boolean dunRequired) {
+    public void updateMobileRequiresDun(boolean dunRequired) {
         final boolean valueChanged = (mDunRequired != dunRequired);
         mDunRequired = dunRequired;
         if (valueChanged && mobileNetworkRequested()) {
@@ -123,7 +123,10 @@
     }
 
     public void registerMobileNetworkRequest() {
-        if (mMobileNetworkCallback != null) return;
+        if (mMobileNetworkCallback != null) {
+            Log.e(TAG, "registerMobileNetworkRequest() already registered");
+            return;
+        }
 
         final NetworkRequest.Builder builder = new NetworkRequest.Builder()
                 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
@@ -139,11 +142,10 @@
         // Therefore, to avoid duplicate notifications, we only register a no-op.
         mMobileNetworkCallback = new NetworkCallback();
 
-        // TODO: Change the timeout from 0 (no onUnavailable callback) to use some
-        // moderate callback time (once timeout callbacks are implemented). This might
-        // be useful for updating some UI. Additionally, we should definitely log a
-        // message to aid in any subsequent debugging
-        if (DBG) Log.d(TAG, "requesting mobile upstream network: " + mobileUpstreamRequest);
+        // TODO: Change the timeout from 0 (no onUnavailable callback) to some
+        // moderate callback timeout. This might be useful for updating some UI.
+        // Additionally, we log a message to aid in any subsequent debugging.
+        Log.d(TAG, "requesting mobile upstream network: " + mobileUpstreamRequest);
 
         // The following use of the legacy type system cannot be removed until
         // after upstream selection no longer finds networks by legacy type.
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index c38cfb5..8c4941d 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -208,6 +208,7 @@
 
     private boolean mOnlyCore;
     private boolean mFirstBoot;
+    private final boolean mRuntimeRestart;
 
     /**
      * Start the sensor service.
@@ -224,6 +225,8 @@
     public SystemServer() {
         // Check for factory test mode.
         mFactoryTestMode = FactoryTest.getMode();
+        // Remember if it's runtime restart(when sys.boot_completed is already set) or reboot
+        mRuntimeRestart = "1".equals(SystemProperties.get("sys.boot_completed"));
     }
 
     private void run() {
@@ -323,6 +326,8 @@
 
             // Create the system service manager.
             mSystemServiceManager = new SystemServiceManager(mSystemContext);
+            mSystemServiceManager.setRuntimeRestarted(mRuntimeRestart);
+            mSystemServiceManager.setFirstBoot(mFirstBoot);
             LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
@@ -836,6 +841,15 @@
                 }
                 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
 
+                // Wifi Service must be started first for wifi-related services.
+                mSystemServiceManager.startService(WIFI_SERVICE_CLASS);
+                mSystemServiceManager.startService(
+                        "com.android.server.wifi.scanner.WifiScanningService");
+
+                if (!disableRtt) {
+                    mSystemServiceManager.startService("com.android.server.wifi.RttService");
+                }
+
                 if (context.getPackageManager().hasSystemFeature(
                         PackageManager.FEATURE_WIFI_AWARE)) {
                     mSystemServiceManager.startService(WIFI_AWARE_SERVICE_CLASS);
@@ -847,13 +861,6 @@
                         PackageManager.FEATURE_WIFI_DIRECT)) {
                     mSystemServiceManager.startService(WIFI_P2P_SERVICE_CLASS);
                 }
-                mSystemServiceManager.startService(WIFI_SERVICE_CLASS);
-                mSystemServiceManager.startService(
-                            "com.android.server.wifi.scanner.WifiScanningService");
-
-                if (!disableRtt) {
-                    mSystemServiceManager.startService("com.android.server.wifi.RttService");
-                }
 
                 if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_ETHERNET) ||
                     mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {
diff --git a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
index 00420e9..1e67769 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.Mockito.reset;
 
 import android.content.Context;
@@ -69,12 +70,13 @@
 
     @Test
     public void testDoesNothingBeforeStarted() {
-        UpstreamNetworkMonitor unm = new UpstreamNetworkMonitor(null, null, EVENT_UNM_UPDATE);
-        assertFalse(unm.mobileNetworkRequested());
-        // Given a null Context, and therefore a null ConnectivityManager,
-        // these would cause an exception, if they actually attempted anything.
-        unm.mobileUpstreamRequiresDun(true);
-        unm.mobileUpstreamRequiresDun(false);
+        assertTrue(mCM.hasNoCallbacks());
+        assertFalse(mUNM.mobileNetworkRequested());
+
+        mUNM.updateMobileRequiresDun(true);
+        assertTrue(mCM.hasNoCallbacks());
+        mUNM.updateMobileRequiresDun(false);
+        assertTrue(mCM.hasNoCallbacks());
     }
 
     @Test
@@ -85,7 +87,7 @@
         assertEquals(1, mCM.trackingDefault.size());
 
         mUNM.stop();
-        assertTrue(mCM.isEmpty());
+        assertTrue(mCM.hasNoCallbacks());
     }
 
     @Test
@@ -97,11 +99,11 @@
         assertTrue(mCM.isListeningForDun());
 
         mUNM.stop();
-        assertTrue(mCM.isEmpty());
+        assertTrue(mCM.hasNoCallbacks());
     }
 
     @Test
-    public void testCanRequestMobileNetwork() throws Exception {
+    public void testRequestsMobileNetwork() throws Exception {
         assertFalse(mUNM.mobileNetworkRequested());
         assertEquals(0, mCM.requested.size());
 
@@ -109,25 +111,22 @@
         assertFalse(mUNM.mobileNetworkRequested());
         assertEquals(0, mCM.requested.size());
 
-        mUNM.mobileUpstreamRequiresDun(false);
+        mUNM.updateMobileRequiresDun(false);
         assertFalse(mUNM.mobileNetworkRequested());
         assertEquals(0, mCM.requested.size());
 
         mUNM.registerMobileNetworkRequest();
         assertTrue(mUNM.mobileNetworkRequested());
-        assertEquals(1, mCM.requested.size());
-        assertEquals(1, mCM.legacyTypeMap.size());
-        assertEquals(Integer.valueOf(TYPE_MOBILE_HIPRI),
-                mCM.legacyTypeMap.values().iterator().next());
+        assertUpstreamTypeRequested(TYPE_MOBILE_HIPRI);
         assertFalse(mCM.isDunRequested());
 
         mUNM.stop();
         assertFalse(mUNM.mobileNetworkRequested());
-        assertTrue(mCM.isEmpty());
+        assertTrue(mCM.hasNoCallbacks());
     }
 
     @Test
-    public void testCanRequestDunNetwork() throws Exception {
+    public void testRequestsDunNetwork() throws Exception {
         assertFalse(mUNM.mobileNetworkRequested());
         assertEquals(0, mCM.requested.size());
 
@@ -135,21 +134,50 @@
         assertFalse(mUNM.mobileNetworkRequested());
         assertEquals(0, mCM.requested.size());
 
-        mUNM.mobileUpstreamRequiresDun(true);
+        mUNM.updateMobileRequiresDun(true);
         assertFalse(mUNM.mobileNetworkRequested());
         assertEquals(0, mCM.requested.size());
 
         mUNM.registerMobileNetworkRequest();
         assertTrue(mUNM.mobileNetworkRequested());
-        assertEquals(1, mCM.requested.size());
-        assertEquals(1, mCM.legacyTypeMap.size());
-        assertEquals(Integer.valueOf(TYPE_MOBILE_DUN),
-                mCM.legacyTypeMap.values().iterator().next());
+        assertUpstreamTypeRequested(TYPE_MOBILE_DUN);
         assertTrue(mCM.isDunRequested());
 
         mUNM.stop();
         assertFalse(mUNM.mobileNetworkRequested());
-        assertTrue(mCM.isEmpty());
+        assertTrue(mCM.hasNoCallbacks());
+    }
+
+    @Test
+    public void testUpdateMobileRequiredDun() throws Exception {
+        mUNM.start();
+
+        // Test going from no-DUN to DUN correctly re-registers callbacks.
+        mUNM.updateMobileRequiresDun(false);
+        mUNM.registerMobileNetworkRequest();
+        assertTrue(mUNM.mobileNetworkRequested());
+        assertUpstreamTypeRequested(TYPE_MOBILE_HIPRI);
+        assertFalse(mCM.isDunRequested());
+        mUNM.updateMobileRequiresDun(true);
+        assertTrue(mUNM.mobileNetworkRequested());
+        assertUpstreamTypeRequested(TYPE_MOBILE_DUN);
+        assertTrue(mCM.isDunRequested());
+
+        // Test going from DUN to no-DUN correctly re-registers callbacks.
+        mUNM.updateMobileRequiresDun(false);
+        assertTrue(mUNM.mobileNetworkRequested());
+        assertUpstreamTypeRequested(TYPE_MOBILE_HIPRI);
+        assertFalse(mCM.isDunRequested());
+
+        mUNM.stop();
+        assertFalse(mUNM.mobileNetworkRequested());
+    }
+
+    private void assertUpstreamTypeRequested(int upstreamType) throws Exception {
+        assertEquals(1, mCM.requested.size());
+        assertEquals(1, mCM.legacyTypeMap.size());
+        assertEquals(Integer.valueOf(upstreamType),
+                mCM.legacyTypeMap.values().iterator().next());
     }
 
     private static class TestConnectivityManager extends ConnectivityManager {
@@ -162,7 +190,7 @@
             super(ctx, svc);
         }
 
-        boolean isEmpty() {
+        boolean hasNoCallbacks() {
             return trackingDefault.isEmpty() &&
                    listening.isEmpty() &&
                    requested.isEmpty() &&
@@ -225,6 +253,8 @@
             } else if (requested.containsKey(cb)) {
                 requested.remove(cb);
                 legacyTypeMap.remove(cb);
+            } else {
+                fail("Unexpected callback removed");
             }
 
             assertFalse(trackingDefault.contains(cb));