diff options
| -rw-r--r-- | packages/Osu2/src/com/android/osu/NetworkConnection.java | 206 | ||||
| -rw-r--r-- | packages/Osu2/src/com/android/osu/ProvisionService.java | 39 | ||||
| -rw-r--r-- | packages/Osu2/tests/Android.mk | 43 | ||||
| -rw-r--r-- | packages/Osu2/tests/AndroidManifest.xml | 38 | ||||
| -rw-r--r-- | packages/Osu2/tests/README.md | 45 | ||||
| -rwxr-xr-x | packages/Osu2/tests/runtests.sh | 24 | ||||
| -rw-r--r-- | packages/Osu2/tests/src/com/android/osu/NetworkConnectionTest.java | 132 |
7 files changed, 526 insertions, 1 deletions
diff --git a/packages/Osu2/src/com/android/osu/NetworkConnection.java b/packages/Osu2/src/com/android/osu/NetworkConnection.java new file mode 100644 index 000000000000..9f5b929e96d3 --- /dev/null +++ b/packages/Osu2/src/com/android/osu/NetworkConnection.java @@ -0,0 +1,206 @@ +/* + * 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 com.android.osu; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Network; +import android.net.NetworkInfo; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.net.wifi.WifiSsid; +import android.os.Handler; +import android.text.TextUtils; +import android.util.Log; + +import java.io.IOException; + +/** + * Responsible for setup/monitor on a Wi-Fi connection. + */ +public class NetworkConnection { + private static final String TAG = "OSU_NetworkConnection"; + + private final WifiManager mWifiManager; + private final Callbacks mCallbacks; + private final int mNetworkId; + private boolean mConnected = false; + + /** + * Callbacks on Wi-Fi connection state changes. + */ + public interface Callbacks { + /** + * Invoked when network connection is established with IP connectivity. + * + * @param network {@link Network} associated with the connected network. + */ + public void onConnected(Network network); + + /** + * Invoked when the targeted network is disconnected. + */ + public void onDisconnected(); + + /** + * Invoked when network connection is not established within the pre-defined timeout. + */ + public void onTimeout(); + } + + /** + * Create an instance of {@link NetworkConnection} for the specified Wi-Fi network. + * The Wi-Fi network (specified by its SSID) will be added/enabled as part of this object + * creation. + * + * {@link #teardown} will need to be invoked once you're done with this connection, + * to remove the given Wi-Fi network from the framework. + * + * @param context The application context + * @param handler The handler to dispatch the processing of received broadcast intents + * @param ssid The SSID to connect to + * @param nai The network access identifier associated with the AP + * @param callbacks The callbacks to be invoked on network change events + * @throws IOException when failed to add/enable the specified Wi-Fi network + */ + public NetworkConnection(Context context, Handler handler, WifiSsid ssid, String nai, + Callbacks callbacks) throws IOException { + mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + mCallbacks = callbacks; + mNetworkId = connect(ssid, nai); + + // TODO(zqiu): setup alarm to timed out the connection attempt. + + IntentFilter filter = new IntentFilter(); + filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); + BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { + handleNetworkStateChanged( + intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO), + intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO)); + } + } + }; + // Provide a Handler so that the onReceive call will be run on the specified handler + // thread instead of the main thread. + context.registerReceiver(receiver, filter, null, handler); + } + + /** + * Teardown the network connection by removing the network. + */ + public void teardown() { + mWifiManager.removeNetwork(mNetworkId); + } + + /** + * Connect to a OSU Wi-Fi network specified by the given SSID. The security type of the Wi-Fi + * network is either open or OSEN (OSU Server-only authenticated layer 2 Encryption Network). + * When network access identifier is provided, OSEN is used. + * + * @param ssid The SSID to connect to + * @param nai Network access identifier of the network + * + * @return unique ID associated with the network + * @throws IOException + */ + private int connect(WifiSsid ssid, String nai) throws IOException { + WifiConfiguration config = new WifiConfiguration(); + config.SSID = "\"" + ssid.toString() + "\""; + if (TextUtils.isEmpty(nai)) { + config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); + } else { + // TODO(zqiu): configuration setup for OSEN. + } + int networkId = mWifiManager.addNetwork(config); + if (networkId < 0) { + throw new IOException("Failed to add OSU network"); + } + if (!mWifiManager.enableNetwork(networkId, true)) { + throw new IOException("Failed to enable OSU network"); + } + return networkId; + } + + /** + * Handle network state changed events. + * + * @param networkInfo {@link NetworkInfo} indicating the current network state + * @param wifiInfo {@link WifiInfo} associated with the current network when connected + */ + private void handleNetworkStateChanged(NetworkInfo networkInfo, WifiInfo wifiInfo) { + if (networkInfo == null) { + Log.e(TAG, "NetworkInfo not provided for network state changed event"); + return; + } + switch (networkInfo.getDetailedState()) { + case CONNECTED: + handleConnectedEvent(wifiInfo); + break; + case DISCONNECTED: + handleDisconnectedEvent(); + break; + default: + Log.d(TAG, "Ignore uninterested state: " + networkInfo.getDetailedState()); + break; + } + } + + /** + * Handle network connected event. + * + * @param wifiInfo {@link WifiInfo} associated with the current connection + */ + private void handleConnectedEvent(WifiInfo wifiInfo) { + if (mConnected) { + // No-op if already connected. + return; + } + if (wifiInfo == null) { + Log.e(TAG, "WifiInfo not provided for connected event"); + return; + } + if (wifiInfo.getNetworkId() != mNetworkId) { + return; + } + Network network = mWifiManager.getCurrentNetwork(); + if (network == null) { + Log.e(TAG, "Current network is not set"); + return; + } + mConnected = true; + mCallbacks.onConnected(network); + } + + /** + * Handle network disconnected event. + */ + private void handleDisconnectedEvent() { + if (!mConnected) { + // No-op if not connected, most likely a disconnect event for a different network. + return; + } + mConnected = false; + mCallbacks.onDisconnected(); + } +} diff --git a/packages/Osu2/src/com/android/osu/ProvisionService.java b/packages/Osu2/src/com/android/osu/ProvisionService.java index 7f683a7a742a..b1d43b2b0ea9 100644 --- a/packages/Osu2/src/com/android/osu/ProvisionService.java +++ b/packages/Osu2/src/com/android/osu/ProvisionService.java @@ -17,6 +17,7 @@ package com.android.osu; import android.content.Context; +import android.net.Network; import android.net.wifi.hotspot2.OsuProvider; import android.os.Handler; import android.os.HandlerThread; @@ -24,6 +25,8 @@ import android.os.Looper; import android.os.Message; import android.util.Log; +import java.io.IOException; + /** * Service responsible for performing Passpoint subscription provisioning tasks. * This service will run on a separate thread to avoid blocking on the Main thread. @@ -38,6 +41,9 @@ public class ProvisionService implements OsuService { private final ServiceHandler mServiceHandler; private final OsuProvider mProvider; + private boolean mStarted = false; + private NetworkConnection mNetworkConnection = null; + public ProvisionService(Context context, OsuProvider provider) { mContext = context; mProvider = provider; @@ -68,9 +74,25 @@ public class ProvisionService implements OsuService { public void handleMessage(Message msg) { switch (msg.what) { case COMMAND_START: - Log.e(TAG, "Start provision service"); + if (mStarted) { + Log.e(TAG, "Service already started"); + return; + } + try { + // Initiate network connection to the OSU AP. + mNetworkConnection = new NetworkConnection( + mContext, this, mProvider.getOsuSsid(), + mProvider.getNetworkAccessIdentifier(), new NetworkCallbacks()); + mStarted = true; + } catch (IOException e) { + // TODO(zqiu): broadcast failure event via LocalBroadcastManager. + } break; case COMMAND_STOP: + if (!mStarted) { + Log.e(TAG, "Service not started"); + return; + } Log.e(TAG, "Stop provision service"); break; default: @@ -79,4 +101,19 @@ public class ProvisionService implements OsuService { } } } + + private final class NetworkCallbacks implements NetworkConnection.Callbacks { + @Override + public void onConnected(Network network) { + Log.d(TAG, "Connected to OSU AP"); + } + + @Override + public void onDisconnected() { + } + + @Override + public void onTimeout() { + } + } } diff --git a/packages/Osu2/tests/Android.mk b/packages/Osu2/tests/Android.mk new file mode 100644 index 000000000000..4b6e0e60652b --- /dev/null +++ b/packages/Osu2/tests/Android.mk @@ -0,0 +1,43 @@ +# 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) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests +LOCAL_CERTIFICATE := platform + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_JAVA_LIBRARIES := android.test.runner + +LOCAL_JACK_FLAGS := --multi-dex native + +LOCAL_PACKAGE_NAME := OsuTests +LOCAL_COMPATIBILITY_SUITE := device-tests + +LOCAL_INSTRUMENTATION_FOR := Osu2 + +LOCAL_STATIC_JAVA_LIBRARIES := \ + android-support-test \ + mockito-target-minus-junit4 \ + frameworks-base-testutils + +# Code coverage puts us over the dex limit, so enable multi-dex for coverage-enabled builds +ifeq (true,$(EMMA_INSTRUMENT)) +LOCAL_JACK_FLAGS := --multi-dex native +LOCAL_DX_FLAGS := --multi-dex +endif # EMMA_INSTRUMENT + +include $(BUILD_PACKAGE) diff --git a/packages/Osu2/tests/AndroidManifest.xml b/packages/Osu2/tests/AndroidManifest.xml new file mode 100644 index 000000000000..e22c1122958a --- /dev/null +++ b/packages/Osu2/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="com.android.osu.tests"> + + <application> + <uses-library android:name="android.test.runner" /> + <activity android:label="OsuTestDummyLabel" + android:name="OsuTestDummyName"> + <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="com.android.osu" + android:label="OSU App Tests"> + </instrumentation> + +</manifest> diff --git a/packages/Osu2/tests/README.md b/packages/Osu2/tests/README.md new file mode 100644 index 000000000000..dbfa79c3d26f --- /dev/null +++ b/packages/Osu2/tests/README.md @@ -0,0 +1,45 @@ +# OSU Unit Tests +This package contains unit tests for the OSU app 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/packages/Osu2/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 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 com.android.osu +runtests.sh -e class com.android.osu.NetworkConnectionTest +``` + +If you manually build and push the test APK to the device you can run tests using + +``` +adb shell am instrument -w 'com.android.osu.tests/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/packages/Osu2/tests/runtests.sh b/packages/Osu2/tests/runtests.sh new file mode 100755 index 000000000000..3513f5b8fc64 --- /dev/null +++ b/packages/Osu2/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/packages/Osu2/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-packages-Osu2-tests + +set -x # print commands + +adb root +adb wait-for-device + +adb install -r -g "$OUT/data/app/OsuTests/OsuTests.apk" + +adb shell am instrument -w "$@" 'com.android.osu.tests/android.support.test.runner.AndroidJUnitRunner' diff --git a/packages/Osu2/tests/src/com/android/osu/NetworkConnectionTest.java b/packages/Osu2/tests/src/com/android/osu/NetworkConnectionTest.java new file mode 100644 index 000000000000..2753249aa32e --- /dev/null +++ b/packages/Osu2/tests/src/com/android/osu/NetworkConnectionTest.java @@ -0,0 +1,132 @@ +/* + * 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 com.android.osu; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkInfo; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.net.wifi.WifiSsid; +import android.os.Handler; +import android.test.suitebuilder.annotation.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; + +import java.io.IOException; + +/** + * Unit tests for {@link com.android.osu.NetworkConnection}. + */ +@SmallTest +public class NetworkConnectionTest { + private static final String TEST_SSID = "TEST SSID"; + private static final String TEST_SSID_WITH_QUOTES = "\"" + TEST_SSID + "\""; + private static final int TEST_NETWORK_ID = 1; + + @Mock Context mContext; + @Mock Handler mHandler; + @Mock WifiManager mWifiManager; + @Mock NetworkConnection.Callbacks mCallbacks; + + @Before + public void setUp() throws Exception { + initMocks(this); + when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifiManager); + } + + /** + * Verify that an IOException will be thrown when failed to add the network. + * + * @throws Exception + */ + @Test(expected = IOException.class) + public void networkAddFailed() throws Exception { + when(mWifiManager.addNetwork(any(WifiConfiguration.class))).thenReturn(-1); + new NetworkConnection(mContext, mHandler, WifiSsid.createFromAsciiEncoded(TEST_SSID), + null, mCallbacks); + } + + /** + * Verify that an IOException will be thrown when failed to enable the network. + * + * @throws Exception + */ + @Test(expected = IOException.class) + public void networkEnableFailed() throws Exception { + when(mWifiManager.addNetwork(any(WifiConfiguration.class))).thenReturn(TEST_NETWORK_ID); + when(mWifiManager.enableNetwork(eq(TEST_NETWORK_ID), eq(true))).thenReturn(false); + new NetworkConnection(mContext, mHandler, WifiSsid.createFromAsciiEncoded(TEST_SSID), + null, mCallbacks); + } + + /** + * Verify that the connection is established after receiving a + * WifiManager.NETWORK_STATE_CHANGED_ACTION intent indicating that we are connected. + * + * @throws Exception + */ + @Test + public void openNetworkConnectionEstablished() throws Exception { + when(mWifiManager.addNetwork(any(WifiConfiguration.class))).thenReturn(TEST_NETWORK_ID); + when(mWifiManager.enableNetwork(eq(TEST_NETWORK_ID), eq(true))).thenReturn(true); + NetworkConnection connection = new NetworkConnection(mContext, mHandler, + WifiSsid.createFromAsciiEncoded(TEST_SSID), null, mCallbacks); + + // Verify the WifiConfiguration being added. + ArgumentCaptor<WifiConfiguration> wifiConfig = + ArgumentCaptor.forClass(WifiConfiguration.class); + verify(mWifiManager).addNetwork(wifiConfig.capture()); + assertEquals(wifiConfig.getValue().SSID, TEST_SSID_WITH_QUOTES); + + // Capture the BroadcastReceiver. + ArgumentCaptor<BroadcastReceiver> receiver = + ArgumentCaptor.forClass(BroadcastReceiver.class); + verify(mContext).registerReceiver(receiver.capture(), any(), any(), any()); + + // Setup intent. + Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION); + NetworkInfo networkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", ""); + networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "", ""); + intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo); + WifiInfo wifiInfo = new WifiInfo(); + wifiInfo.setNetworkId(TEST_NETWORK_ID); + intent.putExtra(WifiManager.EXTRA_WIFI_INFO, wifiInfo); + + // Send intent to the receiver. + Network network = new Network(0); + when(mWifiManager.getCurrentNetwork()).thenReturn(network); + receiver.getValue().onReceive(mContext, intent); + + // Verify we are connected. + verify(mCallbacks).onConnected(eq(network)); + } +} |