diff --git a/api/system-current.txt b/api/system-current.txt index a55156899a66f..7541dc2eeb8e8 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5392,11 +5392,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 @@ -5404,7 +5408,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); + } }