From e722cbd451211ef53496f8b245e1ec934617ab99 Mon Sep 17 00:00:00 2001 From: Hai Shalom Date: Wed, 4 Dec 2019 15:50:07 -0800 Subject: [PATCH] [DPP R2] Added support for DPP R2 events Added support for DPP R2 events that provide additional details about the onboarding process of a remote enrollee. Specifically, DPP R2 configurator waits for response from the enrollee, which reports back the SSID, tried channels and band support in case it cannot find the AP. When it reports success, then it means that it is acutally connected. Bug: 139381558 Test: Manual tests with DPP R1 and R2 enrollees Test: atest DppManagerTest Test: atest WifiManagerTest Test: atest EasyConnectStatusCallbackTest Test: act.py -c ../WifiDppConfig.json -tc WifiDppTest Change-Id: I55a8b1e25737e90e304f1933d09ffcd6bcae6ef9 --- api/system-current.txt | 9 +- .../net/wifi/EasyConnectStatusCallback.java | 102 ++++++++++++++---- wifi/java/android/net/wifi/IDppCallback.aidl | 2 +- wifi/java/android/net/wifi/WifiManager.java | 86 ++++++++++++++- .../wifi/EasyConnectStatusCallbackTest.java | 84 +++++++++++++++ .../src/android/net/wifi/WifiManagerTest.java | 60 +++++++++++ 6 files changed, 320 insertions(+), 23 deletions(-) create mode 100644 wifi/tests/src/android/net/wifi/EasyConnectStatusCallbackTest.java diff --git a/api/system-current.txt b/api/system-current.txt index 8a147bbe5b08b..4944c2418937a 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5361,11 +5361,15 @@ package android.net.wifi { ctor public EasyConnectStatusCallback(); method public abstract void onConfiguratorSuccess(int); method public abstract void onEnrolleeSuccess(int); - method public abstract void onFailure(int); + method public void onFailure(int); + method public void onFailure(int, @Nullable String, @NonNull android.util.SparseArray, @NonNull int[]); method public abstract void onProgress(int); field public static final int EASY_CONNECT_EVENT_FAILURE_AUTHENTICATION = -2; // 0xfffffffe field public static final int EASY_CONNECT_EVENT_FAILURE_BUSY = -5; // 0xfffffffb + field public static final int EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK = -10; // 0xfffffff6 field public static final int EASY_CONNECT_EVENT_FAILURE_CONFIGURATION = -4; // 0xfffffffc + field public static final int EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION = -11; // 0xfffffff5 + field public static final int EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION = -12; // 0xfffffff4 field public static final int EASY_CONNECT_EVENT_FAILURE_GENERIC = -7; // 0xfffffff9 field public static final int EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK = -9; // 0xfffffff7 field public static final int EASY_CONNECT_EVENT_FAILURE_INVALID_URI = -1; // 0xffffffff @@ -5373,7 +5377,10 @@ package android.net.wifi { field public static final int EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED = -8; // 0xfffffff8 field public static final int EASY_CONNECT_EVENT_FAILURE_TIMEOUT = -6; // 0xfffffffa field public static final int EASY_CONNECT_EVENT_PROGRESS_AUTHENTICATION_SUCCESS = 0; // 0x0 + field public static final int EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_ACCEPTED = 3; // 0x3 + field public static final int EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_SENT_WAITING_RESPONSE = 2; // 0x2 field public static final int EASY_CONNECT_EVENT_PROGRESS_RESPONSE_PENDING = 1; // 0x1 + field public static final int EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_APPLIED = 1; // 0x1 field public static final int EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT = 0; // 0x0 } diff --git a/wifi/java/android/net/wifi/EasyConnectStatusCallback.java b/wifi/java/android/net/wifi/EasyConnectStatusCallback.java index b8c82fd9e0ae8..4fa93ee271e97 100644 --- a/wifi/java/android/net/wifi/EasyConnectStatusCallback.java +++ b/wifi/java/android/net/wifi/EasyConnectStatusCallback.java @@ -17,32 +17,46 @@ package android.net.wifi; import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; -import android.os.Handler; +import android.util.SparseArray; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; /** * Easy Connect (DPP) Status Callback. Use this callback to get status updates (success, failure, * progress) from the Easy Connect operation started with - * {@link WifiManager#startEasyConnectAsConfiguratorInitiator(String, - * int, int, Handler, EasyConnectStatusCallback)} or - * {@link WifiManager#startEasyConnectAsEnrolleeInitiator(String, - * Handler, EasyConnectStatusCallback)} + * {@link WifiManager#startEasyConnectAsConfiguratorInitiator(String, int, int, Executor, + * EasyConnectStatusCallback)} or {@link WifiManager#startEasyConnectAsEnrolleeInitiator(String, + * Executor, EasyConnectStatusCallback)} * * @hide */ @SystemApi public abstract class EasyConnectStatusCallback { /** - * Easy Connect Success event: Configuration sent (Configurator mode). + * Easy Connect R1 Success event: Configuration sent (Configurator mode). This is the last + * and final Easy Connect event when either the local device or remote device implement R1. + * If both devices implement R2, this event will never be received, and the + * {@link EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_APPLIED} will be received. */ public static final int EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT = 0; + /** + * East Connect R2 Success event: Configuration applied by Enrollee (Configurator mode). + * This is the last and final Easy Connect event when both the local device and remote device + * implement R2. If either the local device or remote device implement R1, this event will never + * be received, and the {@link EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT} will be received. + */ + public static final int EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_APPLIED = 1; + /** @hide */ @IntDef(prefix = {"EASY_CONNECT_EVENT_SUCCESS_"}, value = { EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT, + EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_APPLIED, }) @Retention(RetentionPolicy.SOURCE) public @interface EasyConnectSuccessStatusCode { @@ -58,10 +72,22 @@ public abstract class EasyConnectStatusCallback { */ public static final int EASY_CONNECT_EVENT_PROGRESS_RESPONSE_PENDING = 1; + /** + * Easy Connect R2 Progress event: Configuration sent to Enrollee, waiting for response + */ + public static final int EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_SENT_WAITING_RESPONSE = 2; + + /** + * Easy Connect R2 Progress event: Configuration accepted by Enrollee, waiting for response + */ + public static final int EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_ACCEPTED = 3; + /** @hide */ @IntDef(prefix = {"EASY_CONNECT_EVENT_PROGRESS_"}, value = { EASY_CONNECT_EVENT_PROGRESS_AUTHENTICATION_SUCCESS, EASY_CONNECT_EVENT_PROGRESS_RESPONSE_PENDING, + EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_SENT_WAITING_RESPONSE, + EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_ACCEPTED, }) @Retention(RetentionPolicy.SOURCE) public @interface EasyConnectProgressStatusCode { @@ -114,6 +140,20 @@ public abstract class EasyConnectStatusCallback { */ public static final int EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK = -9; + /** + * Easy Connect R2 Failure event: Enrollee cannot find the network. + */ + public static final int EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK = -10; + + /** + * Easy Connect R2 Failure event: Enrollee failed to authenticate with the network. + */ + public static final int EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION = -11; + + /** + * Easy Connect R2 Failure event: Enrollee rejected the configuration. + */ + public static final int EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION = -12; /** @hide */ @IntDef(prefix = {"EASY_CONNECT_EVENT_FAILURE_"}, value = { @@ -126,6 +166,9 @@ public abstract class EasyConnectStatusCallback { EASY_CONNECT_EVENT_FAILURE_GENERIC, EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED, EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK, + EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK, + EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION, + EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION, }) @Retention(RetentionPolicy.SOURCE) public @interface EasyConnectFailureStatusCode { @@ -138,9 +181,8 @@ public abstract class EasyConnectStatusCallback { * current Easy Connect * session, and no further callbacks will be called. This callback is the successful outcome * of a Easy Connect flow starting with - * {@link WifiManager#startEasyConnectAsEnrolleeInitiator(String, - * Handler, - * EasyConnectStatusCallback)}. + * {@link WifiManager#startEasyConnectAsEnrolleeInitiator(String, Executor, + * EasyConnectStatusCallback)} . * * @param newNetworkId New Wi-Fi configuration with a network ID received from the configurator */ @@ -148,13 +190,11 @@ public abstract class EasyConnectStatusCallback { /** * Called when a Easy Connect success event takes place, except for when configuration is - * received from - * an external Configurator. The callback onSuccessConfigReceived will be used in this case. - * This callback marks the successful end of the current Easy Connect session, and no further - * callbacks will be called. This callback is the successful outcome of a Easy Connect flow - * starting with - * {@link WifiManager#startEasyConnectAsConfiguratorInitiator(String, int, int, Handler, - * EasyConnectStatusCallback)}. + * received from an external Configurator. The callback onSuccessConfigReceived will be used in + * this case. This callback marks the successful end of the current Easy Connect session, and no + * further callbacks will be called. This callback is the successful outcome of a Easy Connect + * flow starting with {@link WifiManager#startEasyConnectAsConfiguratorInitiator(String, int, + * int, Executor,EasyConnectStatusCallback)}. * * @param code Easy Connect success status code. */ @@ -162,12 +202,36 @@ public abstract class EasyConnectStatusCallback { /** * Called when a Easy Connect Failure event takes place. This callback marks the unsuccessful - * end of the - * current Easy Connect session, and no further callbacks will be called. + * end of the current Easy Connect session, and no further callbacks will be called. * * @param code Easy Connect failure status code. */ - public abstract void onFailure(@EasyConnectFailureStatusCode int code); + public void onFailure(@EasyConnectFailureStatusCode int code) {} + + /** + * Called when a Easy Connect Failure event takes place. This callback marks the unsuccessful + * end of the current Easy Connect session, and no further callbacks will be called. + * + * Note: Easy Connect (DPP) R2, provides additional details for the Configurator when the + * remote Enrollee is unable to connect to a network. The ssid, channelList and bandList + * inputs are initialized only for the EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK failure + * code, and the ssid and bandList are initialized for the + * EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION failure code. + * + * @param code Easy Connect failure status code. + * @param ssid SSID of the network the Enrollee tried to connect to. + * @param channelListArray List of Global Operating classes and channel sets the Enrollee used + * to scan to find the network, see the "DPP Connection Status Object" + * section in the specification for the format, and Table E-4 in + * IEEE Std 802.11-2016 - Global operating classes for more details. + * @param operatingClassArray Array of bands the Enrollee supports as expressed as the Global + * Operating Class, see Table E-4 in IEEE Std 802.11-2016 - Global + * operating classes. + */ + public void onFailure(@EasyConnectFailureStatusCode int code, @Nullable String ssid, + @NonNull SparseArray channelListArray, @NonNull int[] operatingClassArray) { + onFailure(code); + } /** * Called when Easy Connect events that indicate progress take place. Can be used by UI elements diff --git a/wifi/java/android/net/wifi/IDppCallback.aidl b/wifi/java/android/net/wifi/IDppCallback.aidl index c452c7664c129..d7a958a5b4b1b 100644 --- a/wifi/java/android/net/wifi/IDppCallback.aidl +++ b/wifi/java/android/net/wifi/IDppCallback.aidl @@ -38,7 +38,7 @@ oneway interface IDppCallback /** * Called when DPP Failure events take place. */ - void onFailure(int status); + void onFailure(int status, String ssid, String channelList, in int[] bandArray); /** * Called when DPP events that indicate progress take place. Can be used by UI elements diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index b98d64dc78f11..5ab05834cd780 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -54,6 +54,7 @@ import android.text.TextUtils; import android.util.CloseGuard; import android.util.Log; import android.util.Pair; +import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -70,6 +71,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.StringTokenizer; import java.util.concurrent.Executor; /** @@ -5177,6 +5179,7 @@ public class WifiManager { @Override public void onSuccessConfigReceived(int newNetworkId) { Log.d(TAG, "Easy Connect onSuccessConfigReceived callback"); + Binder.clearCallingIdentity(); mExecutor.execute(() -> { mEasyConnectStatusCallback.onEnrolleeSuccess(newNetworkId); }); @@ -5185,22 +5188,28 @@ public class WifiManager { @Override public void onSuccess(int status) { Log.d(TAG, "Easy Connect onSuccess callback"); + Binder.clearCallingIdentity(); mExecutor.execute(() -> { mEasyConnectStatusCallback.onConfiguratorSuccess(status); }); } @Override - public void onFailure(int status) { + public void onFailure(int status, String ssid, String channelList, + int[] operatingClassArray) { Log.d(TAG, "Easy Connect onFailure callback"); + Binder.clearCallingIdentity(); mExecutor.execute(() -> { - mEasyConnectStatusCallback.onFailure(status); + SparseArray channelListArray = parseDppChannelList(channelList); + mEasyConnectStatusCallback.onFailure(status, ssid, channelListArray, + operatingClassArray); }); } @Override public void onProgress(int status) { Log.d(TAG, "Easy Connect onProgress callback"); + Binder.clearCallingIdentity(); mExecutor.execute(() -> { mEasyConnectStatusCallback.onProgress(status); }); @@ -5532,4 +5541,77 @@ public class WifiManager { throw e.rethrowFromSystemServer(); } } + + /** + * Parse the list of channels the DPP enrollee reports when it fails to find an AP. + * + * @param channelList List of channels in the format defined in the DPP specification. + * @return A parsed sparse array, where the operating class is the key. + * @hide + */ + @VisibleForTesting + public static SparseArray parseDppChannelList(String channelList) { + SparseArray channelListArray = new SparseArray<>(); + + if (TextUtils.isEmpty(channelList)) { + return channelListArray; + } + StringTokenizer str = new StringTokenizer(channelList, ","); + String classStr = null; + List channelsInClass = new ArrayList<>(); + + try { + while (str.hasMoreElements()) { + String cur = str.nextToken(); + + /** + * Example for a channel list: + * + * 81/1,2,3,4,5,6,7,8,9,10,11,115/36,40,44,48,118/52,56,60,64,121/100,104,108,112, + * 116,120,124,128,132,136,140,0/144,124/149,153,157,161,125/165 + * + * Detect operating class by the delimiter of '/' and use a string tokenizer with + * ',' as a delimiter. + */ + int classDelim = cur.indexOf('/'); + if (classDelim != -1) { + if (classStr != null) { + // Store the last channel array in the sparse array, where the operating + // class is the key (as an integer). + int[] channelsArray = new int[channelsInClass.size()]; + for (int i = 0; i < channelsInClass.size(); i++) { + channelsArray[i] = channelsInClass.get(i); + } + channelListArray.append(Integer.parseInt(classStr), channelsArray); + channelsInClass = new ArrayList<>(); + } + + // Init a new operating class and store the first channel + classStr = cur.substring(0, classDelim); + String channelStr = cur.substring(classDelim + 1); + channelsInClass.add(Integer.parseInt(channelStr)); + } else { + if (classStr == null) { + // Invalid format + Log.e(TAG, "Cannot parse DPP channel list"); + return new SparseArray<>(); + } + channelsInClass.add(Integer.parseInt(cur)); + } + } + + // Store the last array + if (classStr != null) { + int[] channelsArray = new int[channelsInClass.size()]; + for (int i = 0; i < channelsInClass.size(); i++) { + channelsArray[i] = channelsInClass.get(i); + } + channelListArray.append(Integer.parseInt(classStr), channelsArray); + } + return channelListArray; + } catch (NumberFormatException e) { + Log.e(TAG, "Cannot parse DPP channel list"); + return new SparseArray<>(); + } + } } diff --git a/wifi/tests/src/android/net/wifi/EasyConnectStatusCallbackTest.java b/wifi/tests/src/android/net/wifi/EasyConnectStatusCallbackTest.java new file mode 100644 index 0000000000000..b10141434b0ba --- /dev/null +++ b/wifi/tests/src/android/net/wifi/EasyConnectStatusCallbackTest.java @@ -0,0 +1,84 @@ +/* + * 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.wifi; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.util.SparseArray; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; + +/** + * Unit tests for {@link android.net.wifi.EasyConnectStatusCallbackTest}. + */ +@SmallTest +public class EasyConnectStatusCallbackTest { + private EasyConnectStatusCallback mEasyConnectStatusCallback = new EasyConnectStatusCallback() { + @Override + public void onEnrolleeSuccess(int newNetworkId) { + + } + + @Override + public void onConfiguratorSuccess(int code) { + + } + + @Override + public void onProgress(int code) { + + } + + @Override + public void onFailure(int code) { + mOnFailureR1EventReceived = true; + mLastCode = code; + } + }; + private boolean mOnFailureR1EventReceived; + private int mLastCode; + + @Before + public void setUp() { + mOnFailureR1EventReceived = false; + mLastCode = 0; + } + + /** + * Test that the legacy R1 onFailure is called by default if the R2 onFailure is not overridden + * by the app. + */ + @Test + public void testR1OnFailureCalled() { + + SparseArray channelList = new SparseArray<>(); + int[] channelArray = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; + + channelList.append(81, channelArray); + mEasyConnectStatusCallback.onFailure( + EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK, + "SomeSSID", channelList, new int[] {81}); + + assertTrue(mOnFailureR1EventReceived); + assertEquals(mLastCode, + EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK); + } +} diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java index 4a46744a2d320..821661196f4fe 100644 --- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -89,6 +89,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.connectivity.WifiActivityEnergyInfo; import android.os.test.TestLooper; +import android.util.SparseArray; import androidx.test.filters.SmallTest; @@ -2089,4 +2090,63 @@ public class WifiManagerTest { .thenReturn(new Long(~WifiManager.WIFI_FEATURE_WAPI)); assertFalse(mWifiManager.isWapiSupported()); } + + /* + * Test that DPP channel list is parsed correctly + */ + @Test + public void testparseDppChannelList() throws Exception { + String channelList = "81/1,2,3,4,5,6,7,8,9,10,11,115/36,40,44,48"; + SparseArray expectedResult = new SparseArray<>(); + expectedResult.append(81, new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}); + expectedResult.append(115, new int[]{36, 40, 44, 48}); + + SparseArray result = WifiManager.parseDppChannelList(channelList); + assertEquals(expectedResult.size(), result.size()); + + int index = 0; + int key; + + // Compare the two primitive int arrays + do { + try { + key = result.keyAt(index); + } catch (java.lang.ArrayIndexOutOfBoundsException e) { + break; + } + int[] expected = expectedResult.get(key); + int[] output = result.get(key); + assertEquals(expected.length, output.length); + for (int i = 0; i < output.length; i++) { + assertEquals(expected[i], output[i]); + } + index++; + } while (true); + } + + /* + * Test that DPP channel list parser gracefully fails for invalid input + */ + @Test + public void testparseDppChannelListWithInvalidFormats() throws Exception { + String channelList = "1,2,3,4,5,6,7,8,9,10,11,36,40,44,48"; + SparseArray result = WifiManager.parseDppChannelList(channelList); + assertEquals(result.size(), 0); + + channelList = "ajgalskgjalskjg3-09683dh"; + result = WifiManager.parseDppChannelList(channelList); + assertEquals(result.size(), 0); + + channelList = "13/abc,46////"; + result = WifiManager.parseDppChannelList(channelList); + assertEquals(result.size(), 0); + + channelList = "11/4,5,13/"; + result = WifiManager.parseDppChannelList(channelList); + assertEquals(result.size(), 0); + + channelList = "/24,6"; + result = WifiManager.parseDppChannelList(channelList); + assertEquals(result.size(), 0); + } }