Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b0bb4949 authored by Peter Qiu's avatar Peter Qiu
Browse files

Osu2: add class for managing Wi-Fi network connection

Also setup a test app for unit tests, with few test
cases added for NetworkConnection.

Bug: 62388032
Test: start OSU app with Boigno OSU provider via adb,
      verify the OSU app is started and connected to
      the Boingo OSU AP.
Test: frameworks/base/packages/Osu2/tests/runtests.sh

Change-Id: Id60bc85871f3251d83d779d430e1dd1eff47d86e
parent 5c65738e
Loading
Loading
Loading
Loading
+206 −0
Original line number Diff line number Diff line
/*
 * 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();
    }
}
+38 −1
Original line number Diff line number Diff line
@@ -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() {
        }
    }
}
+43 −0
Original line number Diff line number Diff line
# 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)
+38 −0
Original line number Diff line number Diff line
<?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>
+45 −0
Original line number Diff line number Diff line
# 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`.
Loading