android.net.lowpan: Added basic unit tests.

This change has all of the bits for supporting LoWPAN unit tests.
Additional tests are being written to improve code coverage, but this
seems like a good start.

Bug: b/33073713
Test: Successfully ran enclosed unit tests
Change-Id: Ib3750be5052bf1a90bf871756e9121b047d3871f
diff --git a/lowpan/Android.mk b/lowpan/Android.mk
index 9e9164f..0079958 100644
--- a/lowpan/Android.mk
+++ b/lowpan/Android.mk
@@ -28,4 +28,6 @@
 LOCAL_AIDL_INCLUDES += frameworks/base/core/java
 LOCAL_SRC_FILES += $(call all-Iaidl-files-under, java/android/net/lowpan)
 include $(BUILD_SHARED_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
 endif
diff --git a/lowpan/tests/Android.mk b/lowpan/tests/Android.mk
new file mode 100644
index 0000000..bb0a944
--- /dev/null
+++ b/lowpan/tests/Android.mk
@@ -0,0 +1,65 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+# Make test APK
+# ============================================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+# This list is generated from the java source files in this module
+# The list is a comma separated list of class names with * matching zero or more characters.
+# Example:
+#   Input files: src/com/android/server/lowpan/Test.java src/com/android/server/lowpan/AnotherTest.java
+#   Generated exclude list: com.android.server.lowpan.Test*,com.android.server.lowpan.AnotherTest*
+
+# Filter all src files to just java files
+local_java_files := $(filter %.java,$(LOCAL_SRC_FILES))
+# Transform java file names into full class names.
+# This only works if the class name matches the file name and the directory structure
+# matches the package.
+local_classes := $(subst /,.,$(patsubst src/%.java,%,$(local_java_files)))
+# Utility variables to allow replacing a space with a comma
+comma:= ,
+empty:=
+space:= $(empty) $(empty)
+# Convert class name list to jacoco exclude list
+# This appends a * to all classes and replace the space separators with commas.
+# These patterns will match all classes in this module and their inner classes.
+jacoco_exclude := $(subst $(space),$(comma),$(patsubst %,%*,$(local_classes)))
+
+jacoco_include := android.net.lowpan.*
+
+LOCAL_JACK_COVERAGE_INCLUDE_FILTER := $(jacoco_include)
+LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := $(jacoco_exclude)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+	android-support-test \
+	guava \
+	mockito-target-minus-junit4 \
+	frameworks-base-testutils \
+
+LOCAL_JAVA_LIBRARIES := \
+	android.test.runner \
+
+LOCAL_PACKAGE_NAME := FrameworksLowpanApiTests
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
+LOCAL_CERTIFICATE := platform
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+include $(BUILD_PACKAGE)
diff --git a/lowpan/tests/AndroidManifest.xml b/lowpan/tests/AndroidManifest.xml
new file mode 100644
index 0000000..a216214
--- /dev/null
+++ b/lowpan/tests/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ 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
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.net.lowpan.test">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:label="LowpanTestDummyLabel"
+                  android:name="LowpanTestDummyName">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.net.lowpan.test"
+        android:label="Frameworks LoWPAN API Tests">
+    </instrumentation>
+
+</manifest>
diff --git a/lowpan/tests/AndroidTest.xml b/lowpan/tests/AndroidTest.xml
new file mode 100644
index 0000000..72ad050
--- /dev/null
+++ b/lowpan/tests/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs Frameworks LoWPAN API Tests.">
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="FrameworksLowpanApiTests.apk" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-tag" value="FrameworksLowpanApiTests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.net.lowpan.test" />
+        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
diff --git a/lowpan/tests/README.md b/lowpan/tests/README.md
new file mode 100644
index 0000000..d0eed95
--- /dev/null
+++ b/lowpan/tests/README.md
@@ -0,0 +1,50 @@
+# LoWPAN Unit Tests
+This package contains unit tests for the android LoWPAN framework System APIs based on the
+[Android Testing Support Library](http://developer.android.com/tools/testing-support-library/index.html).
+The test cases are built using the [JUnit](http://junit.org/) and [Mockito](http://mockito.org/)
+libraries.
+
+## Running Tests
+The easiest way to run tests is simply run
+
+```
+frameworks/base/lowpan/tests/runtests.sh
+```
+
+`runtests.sh` will build the test project and all of its dependencies and push the APK to the
+connected device. It will then run the tests on the device.
+
+To pick up changes in framework/base, you will need to:
+1. rebuild the framework library 'make -j32'
+2. sync over the updated library to the device 'adb sync'
+3. restart framework on the device 'adb shell stop' then 'adb shell start'
+
+To enable syncing data to the device for first time after clean reflash:
+1. adb disable-verity
+2. adb reboot
+3. adb remount
+
+See below for a few example of options to limit which tests are run.
+See the
+[AndroidJUnitRunner Documentation](https://developer.android.com/reference/android/support/test/runner/AndroidJUnitRunner.html)
+for more details on the supported options.
+
+```
+runtests.sh -e package android.net.lowpan
+runtests.sh -e class android.net.lowpan.LowpanManagerTest
+```
+
+If you manually build and push the test APK to the device you can run tests using
+
+```
+adb shell am instrument -w 'android.net.wifi.test/android.support.test.runner.AndroidJUnitRunner'
+```
+
+## Adding Tests
+Tests can be added by adding classes to the src directory. JUnit4 style test cases can
+be written by simply annotating test methods with `org.junit.Test`.
+
+## Debugging Tests
+If you are trying to debug why tests are not doing what you expected, you can add android log
+statements and use logcat to view them. The beginning and end of every tests is automatically logged
+with the tag `TestRunner`.
diff --git a/lowpan/tests/runtests.sh b/lowpan/tests/runtests.sh
new file mode 100755
index 0000000..040f4f0
--- /dev/null
+++ b/lowpan/tests/runtests.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+if [ -z $ANDROID_BUILD_TOP ]; then
+  echo "You need to source and lunch before you can use this script"
+  exit 1
+fi
+
+echo "Running tests"
+
+set -e # fail early
+
+echo "+ mmma -j32 $ANDROID_BUILD_TOP/frameworks/base/lowpan/tests"
+# NOTE Don't actually run the command above since this shell doesn't inherit functions from the
+#      caller.
+make -j32 -C $ANDROID_BUILD_TOP -f build/core/main.mk MODULES-IN-frameworks-base-lowpan-tests
+
+set -x # print commands
+
+adb root
+adb wait-for-device
+
+adb install -r -g "$OUT/data/app/FrameworksLowpanApiTests/FrameworksLowpanApiTests.apk"
+
+adb shell am instrument -w "$@" 'android.net.lowpan.test/android.support.test.runner.AndroidJUnitRunner'
diff --git a/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java b/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java
new file mode 100644
index 0000000..455ee08
--- /dev/null
+++ b/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.lowpan;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.*;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.test.TestLooper;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import java.util.Map;
+import java.util.HashMap;
+
+/** Unit tests for android.net.lowpan.LowpanInterface. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class LowpanInterfaceTest {
+    private static final String TEST_PACKAGE_NAME = "TestPackage";
+
+    @Mock Context mContext;
+    @Mock ILowpanInterface mLowpanInterfaceService;
+    @Mock IBinder mLowpanInterfaceBinder;
+    @Mock ApplicationInfo mApplicationInfo;
+    @Mock IBinder mAppBinder;
+    @Mock LowpanInterface.Callback mLowpanInterfaceCallback;
+
+    private Handler mHandler;
+    private final TestLooper mTestLooper = new TestLooper();
+    private ILowpanInterfaceListener mInterfaceListener;
+    private LowpanInterface mLowpanInterface;
+    private Map<String, Object> mPropertyMap;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo);
+        when(mContext.getOpPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        when(mLowpanInterfaceService.getName()).thenReturn("wpan0");
+        when(mLowpanInterfaceService.asBinder()).thenReturn(mLowpanInterfaceBinder);
+
+        mLowpanInterface = new LowpanInterface(mContext, mLowpanInterfaceService, mTestLooper.getLooper());
+    }
+
+    @Test
+    public void testStateChangedCallback() throws Exception {
+        // Register our callback
+        mLowpanInterface.registerCallback(mLowpanInterfaceCallback);
+
+        // Verify a listener was added
+        verify(mLowpanInterfaceService)
+                .addListener(
+                        argThat(
+                                listener -> {
+                                    mInterfaceListener = listener;
+                                    return listener instanceof ILowpanInterfaceListener;
+                                }));
+
+        // Build a changed property map
+        Map<String, Object> changedProperties = new HashMap<>();
+        LowpanProperties.KEY_INTERFACE_STATE.putInMap(changedProperties, LowpanInterface.STATE_OFFLINE);
+
+        // Change some properties
+        mInterfaceListener.onPropertiesChanged(changedProperties);
+        mTestLooper.dispatchAll();
+
+        // Verify that the property was changed
+        verify(mLowpanInterfaceCallback)
+                .onStateChanged(
+                        argThat(stateString -> stateString.equals(LowpanInterface.STATE_OFFLINE)));
+    }
+}
diff --git a/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java b/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java
new file mode 100644
index 0000000..481ad76
--- /dev/null
+++ b/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java
@@ -0,0 +1,176 @@
+/*
+ * 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.lowpan;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.*;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.test.TestLooper;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for android.net.lowpan.LowpanManager. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class LowpanManagerTest {
+    private static final String TEST_PACKAGE_NAME = "TestPackage";
+
+    @Mock Context mContext;
+    @Mock ILowpanManager mLowpanService;
+    @Mock ILowpanInterface mLowpanInterfaceService;
+    @Mock IBinder mLowpanInterfaceBinder;
+    @Mock ApplicationInfo mApplicationInfo;
+    @Mock IBinder mAppBinder;
+    @Mock LowpanManager.Callback mLowpanManagerCallback;
+
+    private Handler mHandler;
+    private final TestLooper mTestLooper = new TestLooper();
+    private LowpanManager mLowpanManager;
+
+    private ILowpanManagerListener mManagerListener;
+    private LowpanInterface mLowpanInterface;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo);
+        when(mContext.getOpPackageName()).thenReturn(TEST_PACKAGE_NAME);
+
+        mLowpanManager = new LowpanManager(mContext, mLowpanService, mTestLooper.getLooper());
+    }
+
+    @Test
+    public void testGetEmptyInterfaceList() throws Exception {
+        when(mLowpanService.getInterfaceList()).thenReturn(new String[0]);
+        assertTrue(mLowpanManager.getInterfaceList().length == 0);
+        assertTrue(mLowpanManager.getInterface() == null);
+    }
+
+    @Test
+    public void testGetInterfaceList() throws Exception {
+        when(mLowpanService.getInterfaceList()).thenReturn(new String[] {"wpan0"});
+        when(mLowpanService.getInterface("wpan0")).thenReturn(mLowpanInterfaceService);
+        when(mLowpanInterfaceService.getName()).thenReturn("wpan0");
+        when(mLowpanInterfaceService.asBinder()).thenReturn(mLowpanInterfaceBinder);
+        assertEquals(mLowpanManager.getInterfaceList().length, 1);
+
+        LowpanInterface iface = mLowpanManager.getInterface();
+        assertNotNull(iface);
+        assertEquals(iface.getName(), "wpan0");
+    }
+
+    @Test
+    public void testRegisterCallback() throws Exception {
+        when(mLowpanInterfaceService.getName()).thenReturn("wpan0");
+        when(mLowpanInterfaceService.asBinder()).thenReturn(mLowpanInterfaceBinder);
+
+        // Register our callback
+        mLowpanManager.registerCallback(mLowpanManagerCallback);
+
+        // Verify a listener was added
+        verify(mLowpanService)
+                .addListener(
+                        argThat(
+                                listener -> {
+                                    mManagerListener = listener;
+                                    return listener instanceof ILowpanManagerListener;
+                                }));
+
+        // Add an interface
+        mManagerListener.onInterfaceAdded(mLowpanInterfaceService);
+        mTestLooper.dispatchAll();
+
+        // Verify that the interface was added
+        verify(mLowpanManagerCallback)
+                .onInterfaceAdded(
+                        argThat(
+                                iface -> {
+                                    mLowpanInterface = iface;
+                                    return iface instanceof LowpanInterface;
+                                }));
+        verifyNoMoreInteractions(mLowpanManagerCallback);
+
+        // This check causes the test to fail with a weird error, but I'm not sure why.
+        assertEquals(mLowpanInterface.getService(), mLowpanInterfaceService);
+
+        // Verify that calling getInterface on the LowpanManager object will yield the same
+        // LowpanInterface object.
+        when(mLowpanService.getInterfaceList()).thenReturn(new String[] {"wpan0"});
+        when(mLowpanService.getInterface("wpan0")).thenReturn(mLowpanInterfaceService);
+        assertEquals(mLowpanManager.getInterface(), mLowpanInterface);
+
+        // Remove the service
+        mManagerListener.onInterfaceRemoved(mLowpanInterfaceService);
+        mTestLooper.dispatchAll();
+
+        // Verify that the interface was removed
+        verify(mLowpanManagerCallback).onInterfaceRemoved(mLowpanInterface);
+    }
+
+    @Test
+    public void testUnregisterCallback() throws Exception {
+        when(mLowpanInterfaceService.getName()).thenReturn("wpan0");
+        when(mLowpanInterfaceService.asBinder()).thenReturn(mLowpanInterfaceBinder);
+
+        // Register our callback
+        mLowpanManager.registerCallback(mLowpanManagerCallback);
+
+        // Verify a listener was added
+        verify(mLowpanService)
+                .addListener(
+                        argThat(
+                                listener -> {
+                                    mManagerListener = listener;
+                                    return listener instanceof ILowpanManagerListener;
+                                }));
+
+        // Add an interface
+        mManagerListener.onInterfaceAdded(mLowpanInterfaceService);
+        mTestLooper.dispatchAll();
+
+        // Verify that the interface was added
+        verify(mLowpanManagerCallback)
+                .onInterfaceAdded(
+                        argThat(
+                                iface -> {
+                                    mLowpanInterface = iface;
+                                    return iface instanceof LowpanInterface;
+                                }));
+        verifyNoMoreInteractions(mLowpanManagerCallback);
+
+        // Unregister our callback
+        mLowpanManager.unregisterCallback(mLowpanManagerCallback);
+
+        // Verify the listener was removed
+        verify(mLowpanService).removeListener(mManagerListener);
+
+        // Verify that the callback wasn't invoked.
+        verifyNoMoreInteractions(mLowpanManagerCallback);
+    }
+}