Merge "Bootstrap IpReachabilityMonitor unit tests" am: a9889c94aa am: 39a707813b
am: 5bc28905b6

Change-Id: I9ca394ccac1683d5d91b426582cfb3cddb5f5e96
diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/services/net/java/android/net/ip/IpReachabilityMonitor.java
index 97c9d82..e833f6a 100644
--- a/services/net/java/android/net/ip/IpReachabilityMonitor.java
+++ b/services/net/java/android/net/ip/IpReachabilityMonitor.java
@@ -16,8 +16,6 @@
 
 package android.net.ip;
 
-import com.android.internal.annotations.GuardedBy;
-
 import android.content.Context;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
@@ -31,18 +29,22 @@
 import android.net.netlink.NetlinkMessage;
 import android.net.netlink.NetlinkSocket;
 import android.net.netlink.RtNetlinkNeighborMessage;
-import android.net.netlink.StructNdaCacheInfo;
 import android.net.netlink.StructNdMsg;
+import android.net.netlink.StructNdaCacheInfo;
 import android.net.netlink.StructNlMsgHdr;
 import android.net.util.MultinetworkPolicyTracker;
 import android.net.util.SharedLog;
 import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
 import android.os.SystemClock;
 import android.system.ErrnoException;
 import android.system.NetlinkSocketAddress;
 import android.system.OsConstants;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.InterruptedIOException;
 import java.net.Inet6Address;
 import java.net.InetAddress;
@@ -147,12 +149,32 @@
         public void notifyLost(InetAddress ip, String logMsg);
     }
 
+    /**
+     * Encapsulates IpReachabilityMonitor depencencies on systems that hinder unit testing.
+     * TODO: consider also wrapping MultinetworkPolicyTracker in this interface.
+     */
+    interface Dependencies {
+        void acquireWakeLock(long durationMs);
+
+        static Dependencies makeDefault(Context context, String iface) {
+            final String lockName = TAG + "." + iface;
+            final PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+            final WakeLock lock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, lockName);
+
+            return new Dependencies() {
+                public void acquireWakeLock(long durationMs) {
+                    lock.acquire(durationMs);
+                }
+            };
+        }
+    }
+
     private final Object mLock = new Object();
-    private final PowerManager.WakeLock mWakeLock;
     private final String mInterfaceName;
     private final int mInterfaceIndex;
     private final SharedLog mLog;
     private final Callback mCallback;
+    private final Dependencies mDependencies;
     private final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
     private final NetlinkSocketObserver mNetlinkSocketObserver;
     private final Thread mObserverThread;
@@ -228,20 +250,20 @@
     }
 
     public IpReachabilityMonitor(Context context, String ifName, SharedLog log, Callback callback,
-            MultinetworkPolicyTracker tracker) throws IllegalArgumentException {
+            MultinetworkPolicyTracker tracker) {
+        this(ifName, getInterfaceIndex(ifName), log, callback, tracker,
+                Dependencies.makeDefault(context, ifName));
+    }
+
+    @VisibleForTesting
+    IpReachabilityMonitor(String ifName, int ifIndex, SharedLog log, Callback callback,
+            MultinetworkPolicyTracker tracker, Dependencies dependencies) {
         mInterfaceName = ifName;
-        int ifIndex = -1;
-        try {
-            NetworkInterface netIf = NetworkInterface.getByName(ifName);
-            mInterfaceIndex = netIf.getIndex();
-        } catch (SocketException | NullPointerException e) {
-            throw new IllegalArgumentException("invalid interface '" + ifName + "': ", e);
-        }
-        mWakeLock = ((PowerManager) context.getSystemService(Context.POWER_SERVICE)).newWakeLock(
-                PowerManager.PARTIAL_WAKE_LOCK, TAG + "." + mInterfaceName);
         mLog = log.forSubComponent(TAG);
         mCallback = callback;
         mMultinetworkPolicyTracker = tracker;
+        mInterfaceIndex = ifIndex;
+        mDependencies = dependencies;
         mNetlinkSocketObserver = new NetlinkSocketObserver();
         mObserverThread = new Thread(mNetlinkSocketObserver);
         mObserverThread.start();
@@ -398,7 +420,7 @@
             // The wakelock we use is (by default) refcounted, and this version
             // of acquire(timeout) queues a release message to keep acquisitions
             // and releases balanced.
-            mWakeLock.acquire(getProbeWakeLockDuration());
+            mDependencies.acquireWakeLock(getProbeWakeLockDuration());
         }
 
         for (InetAddress target : ipProbeList) {
@@ -429,6 +451,19 @@
         return (numUnicastProbes * retransTimeMs) + gracePeriodMs;
     }
 
+    private static int getInterfaceIndex(String ifname) {
+        final NetworkInterface iface;
+        try {
+            iface = NetworkInterface.getByName(ifname);
+        } catch (SocketException e) {
+            throw new IllegalArgumentException("invalid interface '" + ifname + "': ", e);
+        }
+        if (iface == null) {
+            throw new IllegalArgumentException("NetworkInterface was null for " + ifname);
+        }
+        return iface.getIndex();
+    }
+
     private void logEvent(int probeType, int errorCode) {
         int eventType = probeType | (errorCode & 0xff);
         mMetricsLog.log(mInterfaceName, new IpReachabilityEvent(eventType));
diff --git a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java b/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java
new file mode 100644
index 0000000..f849689
--- /dev/null
+++ b/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ip;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+
+import android.net.util.SharedLog;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+
+/**
+ * Tests for IpReachabilityMonitor.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IpReachabilityMonitorTest {
+
+    @Mock IpReachabilityMonitor.Callback mCallback;
+    @Mock IpReachabilityMonitor.Dependencies mDependencies;
+    @Mock SharedLog mLog;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    IpReachabilityMonitor makeMonitor() {
+        return new IpReachabilityMonitor("fake0", 1, mLog, mCallback, null, mDependencies);
+    }
+
+    @Test
+    public void testNothing() {
+        IpReachabilityMonitor monitor = makeMonitor();
+    }
+}