Merge "Kill ConnectivityManager.CONNECTIVITY_ACTION_SUPL" into rvc-dev
diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt
index ee997f1..c3c31f5 100644
--- a/api/module-lib-current.txt
+++ b/api/module-lib-current.txt
@@ -127,9 +127,11 @@
   public static class TetheringManager.TetheringRequest.Builder {
     ctor public TetheringManager.TetheringRequest.Builder(int);
     method @NonNull public android.net.TetheringManager.TetheringRequest build();
+    method @Nullable public android.net.LinkAddress getClientStaticIpv4Address();
+    method @Nullable public android.net.LinkAddress getLocalIpv4Address();
     method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setExemptFromEntitlementCheck(boolean);
     method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setSilentProvisioning(boolean);
-    method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder useStaticIpv4Addresses(@NonNull android.net.LinkAddress);
+    method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setStaticIpv4Addresses(@NonNull android.net.LinkAddress, @NonNull android.net.LinkAddress);
   }
 
 }
diff --git a/api/system-current.txt b/api/system-current.txt
index fd37989..ae48477 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6597,9 +6597,11 @@
   public static class TetheringManager.TetheringRequest.Builder {
     ctor public TetheringManager.TetheringRequest.Builder(int);
     method @NonNull public android.net.TetheringManager.TetheringRequest build();
+    method @Nullable public android.net.LinkAddress getClientStaticIpv4Address();
+    method @Nullable public android.net.LinkAddress getLocalIpv4Address();
     method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setExemptFromEntitlementCheck(boolean);
     method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setSilentProvisioning(boolean);
-    method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder useStaticIpv4Addresses(@NonNull android.net.LinkAddress);
+    method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setStaticIpv4Addresses(@NonNull android.net.LinkAddress, @NonNull android.net.LinkAddress);
   }
 
   public class TrafficStats {
diff --git a/api/test-current.txt b/api/test-current.txt
index 00c8fdc..d107b17 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1976,9 +1976,11 @@
   public static class TetheringManager.TetheringRequest.Builder {
     ctor public TetheringManager.TetheringRequest.Builder(int);
     method @NonNull public android.net.TetheringManager.TetheringRequest build();
+    method @Nullable public android.net.LinkAddress getClientStaticIpv4Address();
+    method @Nullable public android.net.LinkAddress getLocalIpv4Address();
     method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setExemptFromEntitlementCheck(boolean);
     method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setSilentProvisioning(boolean);
-    method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder useStaticIpv4Addresses(@NonNull android.net.LinkAddress);
+    method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setStaticIpv4Addresses(@NonNull android.net.LinkAddress, @NonNull android.net.LinkAddress);
   }
 
   public class TrafficStats {
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index ef6eb38..fc6e639 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -734,6 +734,16 @@
     // CATEGORY: SETTINGS
     // OS: R
     ACTION_SETTINGS_CHANGE_WIFI_HOTSPOT_PASSWORD = 1737;
+
+    // ACTION: Settings > Security > Toggle on Confirm Sim deletion
+    // CATEGORY: SETTINGS
+    // OS: R
+    ACTION_CONFIRM_SIM_DELETION_ON = 1738;
+
+    // ACTION: Settings > Security > Toggle off Confirm Sim deletion
+    // CATEGORY: SETTINGS
+    // OS: R
+    ACTION_CONFIRM_SIM_DELETION_OFF = 1739;
 }
 
 /**
diff --git a/packages/Tethering/common/TetheringLib/api/module-lib-current.txt b/packages/Tethering/common/TetheringLib/api/module-lib-current.txt
index e25d77d..8a7e975 100644
--- a/packages/Tethering/common/TetheringLib/api/module-lib-current.txt
+++ b/packages/Tethering/common/TetheringLib/api/module-lib-current.txt
@@ -117,9 +117,11 @@
   public static class TetheringManager.TetheringRequest.Builder {
     ctor public TetheringManager.TetheringRequest.Builder(int);
     method @NonNull public android.net.TetheringManager.TetheringRequest build();
+    method @Nullable public android.net.LinkAddress getClientStaticIpv4Address();
+    method @Nullable public android.net.LinkAddress getLocalIpv4Address();
     method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setExemptFromEntitlementCheck(boolean);
     method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setSilentProvisioning(boolean);
-    method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder useStaticIpv4Addresses(@NonNull android.net.LinkAddress);
+    method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setStaticIpv4Addresses(@NonNull android.net.LinkAddress, @NonNull android.net.LinkAddress);
   }
 
 }
diff --git a/packages/Tethering/common/TetheringLib/api/system-current.txt b/packages/Tethering/common/TetheringLib/api/system-current.txt
index d6fcb62..ac73953 100644
--- a/packages/Tethering/common/TetheringLib/api/system-current.txt
+++ b/packages/Tethering/common/TetheringLib/api/system-current.txt
@@ -95,9 +95,11 @@
   public static class TetheringManager.TetheringRequest.Builder {
     ctor public TetheringManager.TetheringRequest.Builder(int);
     method @NonNull public android.net.TetheringManager.TetheringRequest build();
+    method @Nullable public android.net.LinkAddress getClientStaticIpv4Address();
+    method @Nullable public android.net.LinkAddress getLocalIpv4Address();
     method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setExemptFromEntitlementCheck(boolean);
     method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setSilentProvisioning(boolean);
-    method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder useStaticIpv4Addresses(@NonNull android.net.LinkAddress);
+    method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setStaticIpv4Addresses(@NonNull android.net.LinkAddress, @NonNull android.net.LinkAddress);
   }
 
 }
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 7f831ce..f2045df 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -512,19 +512,36 @@
                 mBuilderParcel = new TetheringRequestParcel();
                 mBuilderParcel.tetheringType = type;
                 mBuilderParcel.localIPv4Address = null;
+                mBuilderParcel.staticClientAddress = null;
                 mBuilderParcel.exemptFromEntitlementCheck = false;
                 mBuilderParcel.showProvisioningUi = true;
             }
 
             /**
-             * Configure tethering with static IPv4 assignment (with DHCP disabled).
+             * Configure tethering with static IPv4 assignment.
              *
-             * @param localIPv4Address The preferred local IPv4 address to use.
+             * The clientAddress must be in the localIPv4Address prefix. A DHCP server will be
+             * started, but will only be able to offer the client address. The two addresses must
+             * be in the same prefix.
+             *
+             * @param localIPv4Address The preferred local IPv4 link address to use.
+             * @param clientAddress The static client address.
              */
             @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
             @NonNull
-            public Builder useStaticIpv4Addresses(@NonNull final LinkAddress localIPv4Address) {
+            public Builder setStaticIpv4Addresses(@NonNull final LinkAddress localIPv4Address,
+                    @NonNull final LinkAddress clientAddress) {
+                Objects.requireNonNull(localIPv4Address);
+                Objects.requireNonNull(clientAddress);
+                if (localIPv4Address.getPrefixLength() != clientAddress.getPrefixLength()
+                        || !localIPv4Address.isIpv4() || !clientAddress.isIpv4()
+                        || !new IpPrefix(localIPv4Address.toString()).equals(
+                        new IpPrefix(clientAddress.toString()))) {
+                    throw new IllegalArgumentException("Invalid server or client addresses");
+                }
+
                 mBuilderParcel.localIPv4Address = localIPv4Address;
+                mBuilderParcel.staticClientAddress = clientAddress;
                 return this;
             }
 
@@ -549,6 +566,18 @@
             public TetheringRequest build() {
                 return new TetheringRequest(mBuilderParcel);
             }
+
+            /** Get static server address. */
+            @Nullable
+            public LinkAddress getLocalIpv4Address() {
+                return mBuilderParcel.localIPv4Address;
+            }
+
+            /** Get static client address. */
+            @Nullable
+            public LinkAddress getClientStaticIpv4Address() {
+                return mBuilderParcel.staticClientAddress;
+            }
         }
 
         /**
@@ -563,6 +592,7 @@
         public String toString() {
             return "TetheringRequest [ type= " + mRequestParcel.tetheringType
                     + ", localIPv4Address= " + mRequestParcel.localIPv4Address
+                    + ", staticClientAddress= " + mRequestParcel.staticClientAddress
                     + ", exemptFromEntitlementCheck= "
                     + mRequestParcel.exemptFromEntitlementCheck + ", showProvisioningUi= "
                     + mRequestParcel.showProvisioningUi + " ]";
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl b/packages/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl
index bf19d85..c0280d3 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl
@@ -25,6 +25,7 @@
 parcelable TetheringRequestParcel {
     int tetheringType;
     LinkAddress localIPv4Address;
+    LinkAddress staticClientAddress;
     boolean exemptFromEntitlementCheck;
     boolean showProvisioningUi;
 }
diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java
index 6c0c432..433b903 100644
--- a/packages/Tethering/src/android/net/ip/IpServer.java
+++ b/packages/Tethering/src/android/net/ip/IpServer.java
@@ -35,6 +35,7 @@
 import android.net.RouteInfo;
 import android.net.TetheredClient;
 import android.net.TetheringManager;
+import android.net.TetheringRequestParcel;
 import android.net.dhcp.DhcpLeaseParcelable;
 import android.net.dhcp.DhcpServerCallbacks;
 import android.net.dhcp.DhcpServingParamsParcel;
@@ -243,6 +244,10 @@
     private IDhcpServer mDhcpServer;
     private RaParams mLastRaParams;
     private LinkAddress mIpv4Address;
+
+    private LinkAddress mStaticIpv4ServerAddr;
+    private LinkAddress mStaticIpv4ClientAddr;
+
     @NonNull
     private List<TetheredClient> mDhcpLeases = Collections.emptyList();
 
@@ -547,6 +552,8 @@
         // into calls to InterfaceController, shared with startIPv4().
         mInterfaceCtrl.clearIPv4Address();
         mIpv4Address = null;
+        mStaticIpv4ServerAddr = null;
+        mStaticIpv4ClientAddr = null;
     }
 
     private boolean configureIPv4(boolean enabled) {
@@ -557,7 +564,10 @@
         final Inet4Address srvAddr;
         int prefixLen = 0;
         try {
-            if (mInterfaceType == TetheringManager.TETHERING_USB
+            if (mStaticIpv4ServerAddr != null) {
+                srvAddr = (Inet4Address) mStaticIpv4ServerAddr.getAddress();
+                prefixLen = mStaticIpv4ServerAddr.getPrefixLength();
+            } else if (mInterfaceType == TetheringManager.TETHERING_USB
                     || mInterfaceType == TetheringManager.TETHERING_NCM) {
                 srvAddr = (Inet4Address) parseNumericAddress(USB_NEAR_IFACE_ADDR);
                 prefixLen = USB_PREFIX_LENGTH;
@@ -602,10 +612,6 @@
             return false;
         }
 
-        if (!configureDhcp(enabled, srvAddr, prefixLen)) {
-            return false;
-        }
-
         // Directly-connected route.
         final IpPrefix ipv4Prefix = new IpPrefix(mIpv4Address.getAddress(),
                 mIpv4Address.getPrefixLength());
@@ -617,7 +623,8 @@
             mLinkProperties.removeLinkAddress(mIpv4Address);
             mLinkProperties.removeRoute(route);
         }
-        return true;
+
+        return configureDhcp(enabled, srvAddr, prefixLen);
     }
 
     private String getRandomWifiIPv4Address() {
@@ -937,6 +944,13 @@
         mLinkProperties.setInterfaceName(mIfaceName);
     }
 
+    private void maybeConfigureStaticIp(final TetheringRequestParcel request) {
+        if (request == null) return;
+
+        mStaticIpv4ServerAddr = request.localIPv4Address;
+        mStaticIpv4ClientAddr = request.staticClientAddress;
+    }
+
     class InitialState extends State {
         @Override
         public void enter() {
@@ -951,9 +965,11 @@
                     mLastError = TetheringManager.TETHER_ERROR_NO_ERROR;
                     switch (message.arg1) {
                         case STATE_LOCAL_ONLY:
+                            maybeConfigureStaticIp((TetheringRequestParcel) message.obj);
                             transitionTo(mLocalHotspotState);
                             break;
                         case STATE_TETHERED:
+                            maybeConfigureStaticIp((TetheringRequestParcel) message.obj);
                             transitionTo(mTetheredState);
                             break;
                         default:
diff --git a/packages/Tethering/src/android/net/util/TetheringUtils.java b/packages/Tethering/src/android/net/util/TetheringUtils.java
index 5a6d5c1..dd67ddd 100644
--- a/packages/Tethering/src/android/net/util/TetheringUtils.java
+++ b/packages/Tethering/src/android/net/util/TetheringUtils.java
@@ -15,8 +15,11 @@
  */
 package android.net.util;
 
+import android.net.TetheringRequestParcel;
+
 import java.io.FileDescriptor;
 import java.net.SocketException;
+import java.util.Objects;
 
 /**
  * Native methods for tethering utilization.
@@ -38,4 +41,17 @@
     public static int uint16(short s) {
         return s & 0xffff;
     }
+
+    /** Check whether two TetheringRequestParcels are the same. */
+    public static boolean isTetheringRequestEquals(final TetheringRequestParcel request,
+            final TetheringRequestParcel otherRequest) {
+        if (request == otherRequest) return true;
+
+        return request != null && otherRequest != null
+                && request.tetheringType == otherRequest.tetheringType
+                && Objects.equals(request.localIPv4Address, otherRequest.localIPv4Address)
+                && Objects.equals(request.staticClientAddress, otherRequest.staticClientAddress)
+                && request.exemptFromEntitlementCheck == otherRequest.exemptFromEntitlementCheck
+                && request.showProvisioningUi == otherRequest.showProvisioningUi;
+    }
 }
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
index 3d8dbab..6e91892 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
@@ -92,6 +92,7 @@
 import android.net.util.InterfaceSet;
 import android.net.util.PrefixUtils;
 import android.net.util.SharedLog;
+import android.net.util.TetheringUtils;
 import android.net.util.VersionedBroadcastListener;
 import android.net.wifi.WifiClient;
 import android.net.wifi.WifiManager;
@@ -196,6 +197,11 @@
     private final SharedLog mLog = new SharedLog(TAG);
     private final RemoteCallbackList<ITetheringEventCallback> mTetheringEventCallbacks =
             new RemoteCallbackList<>();
+    // Currently active tethering requests per tethering type. Only one of each type can be
+    // requested at a time. After a tethering type is requested, the map keeps tethering parameters
+    // to be used after the interface comes up asynchronously.
+    private final SparseArray<TetheringRequestParcel> mActiveTetheringRequests =
+            new SparseArray<>();
 
     // used to synchronize public access to members
     private final Object mPublicSync;
@@ -487,14 +493,31 @@
     }
 
     void startTethering(final TetheringRequestParcel request, final IIntResultListener listener) {
-        mEntitlementMgr.startProvisioningIfNeeded(request.tetheringType,
-                request.showProvisioningUi);
-        enableTetheringInternal(request.tetheringType, true /* enabled */, listener);
+        mHandler.post(() -> {
+            final TetheringRequestParcel unfinishedRequest = mActiveTetheringRequests.get(
+                    request.tetheringType);
+            // If tethering is already enabled with a different request,
+            // disable before re-enabling.
+            if (unfinishedRequest != null
+                    && !TetheringUtils.isTetheringRequestEquals(unfinishedRequest, request)) {
+                enableTetheringInternal(request.tetheringType, false /* disabled */, null);
+                mEntitlementMgr.stopProvisioningIfNeeded(request.tetheringType);
+            }
+            mActiveTetheringRequests.put(request.tetheringType, request);
+
+            mEntitlementMgr.startProvisioningIfNeeded(request.tetheringType,
+                    request.showProvisioningUi);
+            enableTetheringInternal(request.tetheringType, true /* enabled */, listener);
+        });
     }
 
     void stopTethering(int type) {
-        enableTetheringInternal(type, false /* disabled */, null);
-        mEntitlementMgr.stopProvisioningIfNeeded(type);
+        mHandler.post(() -> {
+            mActiveTetheringRequests.remove(type);
+
+            enableTetheringInternal(type, false /* disabled */, null);
+            mEntitlementMgr.stopProvisioningIfNeeded(type);
+        });
     }
 
     /**
@@ -503,39 +526,45 @@
      */
     private void enableTetheringInternal(int type, boolean enable,
             final IIntResultListener listener) {
-        int result;
+        int result = TETHER_ERROR_NO_ERROR;
         switch (type) {
             case TETHERING_WIFI:
                 result = setWifiTethering(enable);
-                sendTetherResult(listener, result);
                 break;
             case TETHERING_USB:
                 result = setUsbTethering(enable);
-                sendTetherResult(listener, result);
                 break;
             case TETHERING_BLUETOOTH:
                 setBluetoothTethering(enable, listener);
                 break;
             case TETHERING_NCM:
                 result = setNcmTethering(enable);
-                sendTetherResult(listener, result);
                 break;
             case TETHERING_ETHERNET:
                 result = setEthernetTethering(enable);
-                sendTetherResult(listener, result);
                 break;
             default:
                 Log.w(TAG, "Invalid tether type.");
-                sendTetherResult(listener, TETHER_ERROR_UNKNOWN_IFACE);
+                result = TETHER_ERROR_UNKNOWN_IFACE;
+        }
+
+        // The result of Bluetooth tethering will be sent by #setBluetoothTethering.
+        if (type != TETHERING_BLUETOOTH) {
+            sendTetherResult(listener, result, type);
         }
     }
 
-    private void sendTetherResult(final IIntResultListener listener, int result) {
+    private void sendTetherResult(final IIntResultListener listener, final int result,
+            final int type) {
         if (listener != null) {
             try {
                 listener.onResult(result);
             } catch (RemoteException e) { }
         }
+
+        // If changing tethering fail, remove corresponding request
+        // no matter who trigger the start/stop.
+        if (result != TETHER_ERROR_NO_ERROR) mActiveTetheringRequests.remove(type);
     }
 
     private int setWifiTethering(final boolean enable) {
@@ -565,7 +594,7 @@
         if (adapter == null || !adapter.isEnabled()) {
             Log.w(TAG, "Tried to enable bluetooth tethering with null or disabled adapter. null: "
                     + (adapter == null));
-            sendTetherResult(listener, TETHER_ERROR_SERVICE_UNAVAIL);
+            sendTetherResult(listener, TETHER_ERROR_SERVICE_UNAVAIL, TETHERING_BLUETOOTH);
             return;
         }
 
@@ -594,7 +623,7 @@
                 final int result = (((BluetoothPan) proxy).isTetheringOn() == enable)
                         ? TETHER_ERROR_NO_ERROR
                         : TETHER_ERROR_MASTER_ERROR;
-                sendTetherResult(listener, result);
+                sendTetherResult(listener, result, TETHERING_BLUETOOTH);
                 adapter.closeProfileProxy(BluetoothProfile.PAN, proxy);
             }
         }, BluetoothProfile.PAN);
@@ -672,12 +701,18 @@
                 Log.e(TAG, "Tried to Tether an unavailable iface: " + iface + ", ignoring");
                 return TETHER_ERROR_UNAVAIL_IFACE;
             }
-            // NOTE: If a CMD_TETHER_REQUESTED message is already in the TISM's
-            // queue but not yet processed, this will be a no-op and it will not
-            // return an error.
+            // NOTE: If a CMD_TETHER_REQUESTED message is already in the TISM's queue but not yet
+            // processed, this will be a no-op and it will not return an error.
             //
-            // TODO: reexamine the threading and messaging model.
-            tetherState.ipServer.sendMessage(IpServer.CMD_TETHER_REQUESTED, requestedState);
+            // This code cannot race with untether() because they both synchronize on mPublicSync.
+            // TODO: reexamine the threading and messaging model to totally remove mPublicSync.
+            final int type = tetherState.ipServer.interfaceType();
+            final TetheringRequestParcel request = mActiveTetheringRequests.get(type, null);
+            if (request != null) {
+                mActiveTetheringRequests.delete(type);
+            }
+            tetherState.ipServer.sendMessage(IpServer.CMD_TETHER_REQUESTED, requestedState, 0,
+                    request);
             return TETHER_ERROR_NO_ERROR;
         }
     }
diff --git a/packages/Tethering/tests/unit/src/android/net/util/TetheringUtilsTest.java b/packages/Tethering/tests/unit/src/android/net/util/TetheringUtilsTest.java
new file mode 100644
index 0000000..1499f3b
--- /dev/null
+++ b/packages/Tethering/tests/unit/src/android/net/util/TetheringUtilsTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.util;
+
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_WIFI;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.net.LinkAddress;
+import android.net.TetheringRequestParcel;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.MiscAssertsKt;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TetheringUtilsTest {
+    private static final LinkAddress TEST_SERVER_ADDR = new LinkAddress("192.168.43.1/24");
+    private static final LinkAddress TEST_CLIENT_ADDR = new LinkAddress("192.168.43.5/24");
+    private TetheringRequestParcel mTetheringRequest;
+
+    @Before
+    public void setUp() {
+        mTetheringRequest = makeTetheringRequestParcel();
+    }
+
+    public TetheringRequestParcel makeTetheringRequestParcel() {
+        final TetheringRequestParcel request = new TetheringRequestParcel();
+        request.tetheringType = TETHERING_WIFI;
+        request.localIPv4Address = TEST_SERVER_ADDR;
+        request.staticClientAddress = TEST_CLIENT_ADDR;
+        request.exemptFromEntitlementCheck = false;
+        request.showProvisioningUi = true;
+        return request;
+    }
+
+    @Test
+    public void testIsTetheringRequestEquals() throws Exception {
+        TetheringRequestParcel request = makeTetheringRequestParcel();
+
+        assertTrue(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, mTetheringRequest));
+        assertTrue(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request));
+        assertTrue(TetheringUtils.isTetheringRequestEquals(null, null));
+        assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, null));
+        assertFalse(TetheringUtils.isTetheringRequestEquals(null, mTetheringRequest));
+
+        request = makeTetheringRequestParcel();
+        request.tetheringType = TETHERING_USB;
+        assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request));
+
+        request = makeTetheringRequestParcel();
+        request.localIPv4Address = null;
+        request.staticClientAddress = null;
+        assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request));
+
+        request = makeTetheringRequestParcel();
+        request.exemptFromEntitlementCheck = true;
+        assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request));
+
+        request = makeTetheringRequestParcel();
+        request.showProvisioningUi = false;
+        assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request));
+
+        MiscAssertsKt.assertFieldCountEquals(5, TetheringRequestParcel.class);
+    }
+}
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
index 820c852..60d7ad1 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
@@ -82,6 +82,7 @@
 import android.net.ConnectivityManager;
 import android.net.EthernetManager;
 import android.net.EthernetManager.TetheredInterfaceRequest;
+import android.net.IIntResultListener;
 import android.net.INetd;
 import android.net.ITetheringEventCallback;
 import android.net.InetAddresses;
@@ -499,10 +500,16 @@
         return new Tethering(mTetheringDependencies);
     }
 
-    private TetheringRequestParcel createTetheringRquestParcel(final int type) {
+    private TetheringRequestParcel createTetheringRequestParcel(final int type) {
+        return createTetheringRequestParcel(type, null, null);
+    }
+
+    private TetheringRequestParcel createTetheringRequestParcel(final int type,
+            final LinkAddress serverAddr, final LinkAddress clientAddr) {
         final TetheringRequestParcel request = new TetheringRequestParcel();
         request.tetheringType = type;
-        request.localIPv4Address = null;
+        request.localIPv4Address = serverAddr;
+        request.staticClientAddress = clientAddr;
         request.exemptFromEntitlementCheck = false;
         request.showProvisioningUi = false;
 
@@ -616,7 +623,7 @@
 
     private void prepareNcmTethering() {
         // Emulate startTethering(TETHERING_NCM) called
-        mTethering.startTethering(createTetheringRquestParcel(TETHERING_NCM), null);
+        mTethering.startTethering(createTetheringRequestParcel(TETHERING_NCM), null);
         mLooper.dispatchAll();
         verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NCM);
 
@@ -629,7 +636,7 @@
                 .thenReturn(upstreamState);
 
         // Emulate pressing the USB tethering button in Settings UI.
-        mTethering.startTethering(createTetheringRquestParcel(TETHERING_USB), null);
+        mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB), null);
         mLooper.dispatchAll();
         verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
 
@@ -903,7 +910,7 @@
         when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
 
         // Emulate pressing the WiFi tethering button.
-        mTethering.startTethering(createTetheringRquestParcel(TETHERING_WIFI), null);
+        mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), null);
         mLooper.dispatchAll();
         verify(mWifiManager, times(1)).startTetheredHotspot(null);
         verifyNoMoreInteractions(mWifiManager);
@@ -931,7 +938,7 @@
         when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
 
         // Emulate pressing the WiFi tethering button.
-        mTethering.startTethering(createTetheringRquestParcel(TETHERING_WIFI), null);
+        mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), null);
         mLooper.dispatchAll();
         verify(mWifiManager, times(1)).startTetheredHotspot(null);
         verifyNoMoreInteractions(mWifiManager);
@@ -1008,7 +1015,7 @@
         doThrow(new RemoteException()).when(mNetd).ipfwdEnableForwarding(TETHERING_NAME);
 
         // Emulate pressing the WiFi tethering button.
-        mTethering.startTethering(createTetheringRquestParcel(TETHERING_WIFI), null);
+        mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), null);
         mLooper.dispatchAll();
         verify(mWifiManager, times(1)).startTetheredHotspot(null);
         verifyNoMoreInteractions(mWifiManager);
@@ -1303,7 +1310,7 @@
         tetherState = callback.pollTetherStatesChanged();
         assertArrayEquals(tetherState.availableList, new String[] {TEST_WLAN_IFNAME});
 
-        mTethering.startTethering(createTetheringRquestParcel(TETHERING_WIFI), null);
+        mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), null);
         sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
         mLooper.dispatchAll();
         tetherState = callback.pollTetherStatesChanged();
@@ -1398,10 +1405,10 @@
     public void testNoDuplicatedEthernetRequest() throws Exception {
         final TetheredInterfaceRequest mockRequest = mock(TetheredInterfaceRequest.class);
         when(mEm.requestTetheredInterface(any(), any())).thenReturn(mockRequest);
-        mTethering.startTethering(createTetheringRquestParcel(TETHERING_ETHERNET), null);
+        mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), null);
         mLooper.dispatchAll();
         verify(mEm, times(1)).requestTetheredInterface(any(), any());
-        mTethering.startTethering(createTetheringRquestParcel(TETHERING_ETHERNET), null);
+        mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), null);
         mLooper.dispatchAll();
         verifyNoMoreInteractions(mEm);
         mTethering.stopTethering(TETHERING_ETHERNET);
@@ -1580,6 +1587,86 @@
         assertTrue(element + " not found in " + collection, collection.contains(element));
     }
 
+    private class ResultListener extends IIntResultListener.Stub {
+        private final int mExpectedResult;
+        private boolean mHasResult = false;
+        ResultListener(final int expectedResult) {
+            mExpectedResult = expectedResult;
+        }
+
+        @Override
+        public void onResult(final int resultCode) {
+            mHasResult = true;
+            if (resultCode != mExpectedResult) {
+                fail("expected result: " + mExpectedResult + " but actual result: " + resultCode);
+            }
+        }
+
+        public void assertHasResult() {
+            if (!mHasResult) fail("No callback result");
+        }
+    }
+
+    @Test
+    public void testMultipleStartTethering() throws Exception {
+        final LinkAddress serverLinkAddr = new LinkAddress("192.168.20.1/24");
+        final LinkAddress clientLinkAddr = new LinkAddress("192.168.20.42/24");
+        final String serverAddr = "192.168.20.1";
+        final ResultListener firstResult = new ResultListener(TETHER_ERROR_NO_ERROR);
+        final ResultListener secondResult = new ResultListener(TETHER_ERROR_NO_ERROR);
+        final ResultListener thirdResult = new ResultListener(TETHER_ERROR_NO_ERROR);
+
+        // Enable USB tethering and check that Tethering starts USB.
+        mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
+                  null, null), firstResult);
+        mLooper.dispatchAll();
+        firstResult.assertHasResult();
+        verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
+        verifyNoMoreInteractions(mUsbManager);
+
+        // Enable USB tethering again with the same request and expect no change to USB.
+        mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
+                  null, null), secondResult);
+        mLooper.dispatchAll();
+        secondResult.assertHasResult();
+        verify(mUsbManager, never()).setCurrentFunctions(UsbManager.FUNCTION_NONE);
+        reset(mUsbManager);
+
+        // Enable USB tethering with a different request and expect that USB is stopped and
+        // started.
+        mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
+                  serverLinkAddr, clientLinkAddr), thirdResult);
+        mLooper.dispatchAll();
+        thirdResult.assertHasResult();
+        verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
+        verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
+
+        // Expect that when USB comes up, the DHCP server is configured with the requested address.
+        mTethering.interfaceStatusChanged(TEST_USB_IFNAME, true);
+        sendUsbBroadcast(true, true, true, TETHERING_USB);
+        mLooper.dispatchAll();
+        verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
+                any(), any());
+        verify(mNetd).interfaceSetCfg(argThat(cfg -> serverAddr.equals(cfg.ipv4Addr)));
+    }
+
+    @Test
+    public void testRequestStaticServerIp() throws Exception {
+        final LinkAddress serverLinkAddr = new LinkAddress("192.168.20.1/24");
+        final LinkAddress clientLinkAddr = new LinkAddress("192.168.20.42/24");
+        final String serverAddr = "192.168.20.1";
+        mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
+                  serverLinkAddr, clientLinkAddr), null);
+        mLooper.dispatchAll();
+        verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
+        mTethering.interfaceStatusChanged(TEST_USB_IFNAME, true);
+        sendUsbBroadcast(true, true, true, TETHERING_USB);
+        mLooper.dispatchAll();
+        verify(mNetd).interfaceSetCfg(argThat(cfg -> serverAddr.equals(cfg.ipv4Addr)));
+
+        // TODO: test static client address.
+    }
+
     // TODO: Test that a request for hotspot mode doesn't interfere with an
     // already operating tethering mode interface.
 }
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index 8a4a1c6..93bffe9 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -164,6 +164,9 @@
                 }
                 if (DEBUG) Slog.v(TAG, "Storing wallpaper metadata");
                 backupFile(infoStage, data);
+            } else {
+                Slog.w(TAG, "Wallpaper metadata file doesn't exist: " +
+                        mWallpaperFile.getPath());
             }
             if (sysEligible && mWallpaperFile.exists()) {
                 if (sysChanged || !imageStage.exists()) {
@@ -173,6 +176,10 @@
                 if (DEBUG) Slog.v(TAG, "Storing system wallpaper image");
                 backupFile(imageStage, data);
                 prefs.edit().putInt(SYSTEM_GENERATION, sysGeneration).apply();
+            } else {
+                Slog.w(TAG, "Not backing up wallpaper as one of conditions is not "
+                        + "met: sysEligible = " + sysEligible + " wallpaperFileExists = "
+                        + mWallpaperFile.exists());
             }
 
             // If there's no lock wallpaper, then we have nothing to add to the backup.
@@ -181,7 +188,7 @@
                     if (DEBUG) Slog.v(TAG, "Removed lock wallpaper; deleting");
                     lockImageStage.delete();
                 }
-                if (DEBUG) Slog.v(TAG, "No lock paper set, add nothing to backup");
+                Slog.d(TAG, "No lockscreen wallpaper set, add nothing to backup");
                 prefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply();
                 return;
             }
@@ -195,6 +202,12 @@
                 if (DEBUG) Slog.v(TAG, "Storing lock wallpaper image");
                 backupFile(lockImageStage, data);
                 prefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply();
+            } else {
+                Slog.w(TAG, "Not backing up lockscreen wallpaper as one of conditions is not "
+                        + "met: lockEligible = " + lockEligible + " hasLockWallpaper = "
+                        + hasLockWallpaper + " lockWallpaperFileExists = "
+                        + mLockWallpaperFile.exists() + " quotaExceeded (last time) = "
+                        + mQuotaExceeded);
             }
         } catch (Exception e) {
             Slog.e(TAG, "Unable to back up wallpaper", e);
@@ -216,9 +229,7 @@
 
     @Override
     public void onQuotaExceeded(long backupDataBytes, long quotaBytes) {
-        if (DEBUG) {
-            Slog.i(TAG, "Quota exceeded (" + backupDataBytes + " vs " + quotaBytes + ')');
-        }
+        Slog.i(TAG, "Quota exceeded (" + backupDataBytes + " vs " + quotaBytes + ')');
         try (FileOutputStream f = new FileOutputStream(mQuotaFile)) {
             f.write(0);
         } catch (Exception e) {
@@ -230,9 +241,7 @@
     // then post-process in onRestoreFinished() to apply the new wallpaper.
     @Override
     public void onRestoreFinished() {
-        if (DEBUG) {
-            Slog.v(TAG, "onRestoreFinished()");
-        }
+        Slog.v(TAG, "onRestoreFinished()");
         final File filesDir = getFilesDir();
         final File infoStage = new File(filesDir, INFO_STAGE);
         final File imageStage = new File (filesDir, IMAGE_STAGE);
@@ -253,9 +262,7 @@
             // And reset to the wallpaper service we should be using
             ComponentName wpService = parseWallpaperComponent(infoStage, "wp");
             if (servicePackageExists(wpService)) {
-                if (DEBUG) {
-                    Slog.i(TAG, "Using wallpaper service " + wpService);
-                }
+                Slog.i(TAG, "Using wallpaper service " + wpService);
                 mWm.setWallpaperComponent(wpService, UserHandle.USER_SYSTEM);
                 if (!lockImageStage.exists()) {
                     // We have a live wallpaper and no static lock image,
@@ -273,9 +280,7 @@
         } catch (Exception e) {
             Slog.e(TAG, "Unable to restore wallpaper: " + e.getMessage());
         } finally {
-            if (DEBUG) {
-                Slog.v(TAG, "Restore finished; clearing backup bookkeeping");
-            }
+            Slog.v(TAG, "Restore finished; clearing backup bookkeeping");
             infoStage.delete();
             imageStage.delete();
             lockImageStage.delete();
@@ -295,14 +300,14 @@
             // relies on a priori knowledge of the wallpaper info file schema.
             Rect cropHint = parseCropHint(info, hintTag);
             if (cropHint != null) {
-                Slog.i(TAG, "Got restored wallpaper; applying which=" + which);
-                if (DEBUG) {
-                    Slog.v(TAG, "Restored crop hint " + cropHint);
-                }
+                Slog.i(TAG, "Got restored wallpaper; applying which=" + which
+                        + "; cropHint = " + cropHint);
                 try (FileInputStream in = new FileInputStream(stage)) {
                     mWm.setStream(in, cropHint.isEmpty() ? null : cropHint, true, which);
                 } finally {} // auto-closes 'in'
             }
+        } else {
+            Slog.d(TAG, "Restore data doesn't exist for file " + stage.getPath());
         }
     }