Merge "RouteSessionInfo: Remove isValid() and override equals()/hashCode()"
diff --git a/api/system-current.txt b/api/system-current.txt
index b30897f..6da1524 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5848,7 +5848,9 @@
 
   public final class SoftApConfiguration implements android.os.Parcelable {
     method public int describeContents();
+    method @NonNull public java.util.List<android.net.MacAddress> getAllowedClientList();
     method public int getBand();
+    method @NonNull public java.util.List<android.net.MacAddress> getBlockedClientList();
     method @Nullable public android.net.MacAddress getBssid();
     method public int getChannel();
     method public int getMaxNumberOfClients();
@@ -5856,6 +5858,7 @@
     method public int getSecurityType();
     method public int getShutdownTimeoutMillis();
     method @Nullable public String getSsid();
+    method public boolean isClientControlByUserEnabled();
     method public boolean isHiddenSsid();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field public static final int BAND_2GHZ = 1; // 0x1
@@ -5873,9 +5876,11 @@
     ctor public SoftApConfiguration.Builder();
     ctor public SoftApConfiguration.Builder(@NonNull android.net.wifi.SoftApConfiguration);
     method @NonNull public android.net.wifi.SoftApConfiguration build();
+    method @NonNull public android.net.wifi.SoftApConfiguration.Builder enableClientControlByUser(boolean);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setBand(int);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setBssid(@Nullable android.net.MacAddress);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setChannel(int, int);
+    method @NonNull public android.net.wifi.SoftApConfiguration.Builder setClientList(@NonNull java.util.List<android.net.MacAddress>, @NonNull java.util.List<android.net.MacAddress>);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setHiddenSsid(boolean);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setMaxNumberOfClients(int);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setPassphrase(@Nullable String, int);
@@ -6114,6 +6119,8 @@
     field public static final int IFACE_IP_MODE_UNSPECIFIED = -1; // 0xffffffff
     field public static final int PASSPOINT_HOME_NETWORK = 0; // 0x0
     field public static final int PASSPOINT_ROAMING_NETWORK = 1; // 0x1
+    field public static final int SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER = 0; // 0x0
+    field public static final int SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS = 1; // 0x1
     field public static final int SAP_START_FAILURE_GENERAL = 0; // 0x0
     field public static final int SAP_START_FAILURE_NO_CHANNEL = 1; // 0x1
     field public static final int SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION = 2; // 0x2
@@ -6155,6 +6162,7 @@
   }
 
   public static interface WifiManager.SoftApCallback {
+    method public default void onBlockedClientConnecting(@NonNull android.net.wifi.WifiClient, int);
     method public default void onCapabilityChanged(@NonNull android.net.wifi.SoftApCapability);
     method public default void onConnectedClientsChanged(@NonNull java.util.List<android.net.wifi.WifiClient>);
     method public default void onInfoChanged(@NonNull android.net.wifi.SoftApInfo);
diff --git a/wifi/java/android/net/wifi/ISoftApCallback.aidl b/wifi/java/android/net/wifi/ISoftApCallback.aidl
index 482b491..f81bcb9 100644
--- a/wifi/java/android/net/wifi/ISoftApCallback.aidl
+++ b/wifi/java/android/net/wifi/ISoftApCallback.aidl
@@ -55,9 +55,17 @@
 
 
     /**
-     * Service to manager callback providing information of softap.
+     * Service to manager callback providing capability of softap.
      *
      * @param capability is the softap capability. {@link SoftApCapability}
      */
     void onCapabilityChanged(in SoftApCapability capability);
+
+    /**
+     * Service to manager callback providing blocked client of softap with specific reason code.
+     *
+     * @param client the currently blocked client.
+     * @param blockedReason one of blocked reason from {@link WifiManager.SapClientBlockedReason}
+     */
+    void onBlockedClientConnecting(in WifiClient client, int blockedReason);
 }
diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java
index 2b7f8af..a77d30a 100644
--- a/wifi/java/android/net/wifi/SoftApConfiguration.java
+++ b/wifi/java/android/net/wifi/SoftApConfiguration.java
@@ -32,6 +32,9 @@
 import java.lang.annotation.RetentionPolicy;
 import java.nio.charset.CharsetEncoder;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -182,6 +185,22 @@
     private final @SecurityType int mSecurityType;
 
     /**
+     * The flag to indicate client need to authorize by user
+     * when client is connecting to AP.
+     */
+    private final boolean mClientControlByUser;
+
+    /**
+     * The list of blocked client that can't associate to the AP.
+     */
+    private final List<MacAddress> mBlockedClientList;
+
+    /**
+     * The list of allowed client that can associate to the AP.
+     */
+    private final List<MacAddress> mAllowedClientList;
+
+    /**
      * Delay in milliseconds before shutting down soft AP when
      * there are no connected devices.
      */
@@ -219,7 +238,9 @@
     /** Private constructor for Builder and Parcelable implementation. */
     private SoftApConfiguration(@Nullable String ssid, @Nullable MacAddress bssid,
             @Nullable String passphrase, boolean hiddenSsid, @BandType int band, int channel,
-            @SecurityType int securityType, int maxNumberOfClients, int shutdownTimeoutMillis) {
+            @SecurityType int securityType, int maxNumberOfClients, int shutdownTimeoutMillis,
+            boolean clientControlByUser, @NonNull List<MacAddress> blockedList,
+            @NonNull List<MacAddress> allowedList) {
         mSsid = ssid;
         mBssid = bssid;
         mPassphrase = passphrase;
@@ -229,6 +250,9 @@
         mSecurityType = securityType;
         mMaxNumberOfClients = maxNumberOfClients;
         mShutdownTimeoutMillis = shutdownTimeoutMillis;
+        mClientControlByUser = clientControlByUser;
+        mBlockedClientList = new ArrayList<>(blockedList);
+        mAllowedClientList = new ArrayList<>(allowedList);
     }
 
     @Override
@@ -248,13 +272,17 @@
                 && mChannel == other.mChannel
                 && mSecurityType == other.mSecurityType
                 && mMaxNumberOfClients == other.mMaxNumberOfClients
-                && mShutdownTimeoutMillis == other.mShutdownTimeoutMillis;
+                && mShutdownTimeoutMillis == other.mShutdownTimeoutMillis
+                && mClientControlByUser == other.mClientControlByUser
+                && Objects.equals(mBlockedClientList, other.mBlockedClientList)
+                && Objects.equals(mAllowedClientList, other.mAllowedClientList);
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mSsid, mBssid, mPassphrase, mHiddenSsid,
-                mBand, mChannel, mSecurityType, mMaxNumberOfClients, mShutdownTimeoutMillis);
+                mBand, mChannel, mSecurityType, mMaxNumberOfClients, mShutdownTimeoutMillis,
+                mClientControlByUser, mBlockedClientList, mAllowedClientList);
     }
 
     @Override
@@ -270,6 +298,9 @@
         sbuf.append(" \n SecurityType=").append(getSecurityType());
         sbuf.append(" \n MaxClient=").append(mMaxNumberOfClients);
         sbuf.append(" \n ShutdownTimeoutMillis=").append(mShutdownTimeoutMillis);
+        sbuf.append(" \n ClientControlByUser=").append(mClientControlByUser);
+        sbuf.append(" \n BlockedClientList=").append(mBlockedClientList);
+        sbuf.append(" \n AllowedClientList=").append(mAllowedClientList);
         return sbuf.toString();
     }
 
@@ -284,6 +315,9 @@
         dest.writeInt(mSecurityType);
         dest.writeInt(mMaxNumberOfClients);
         dest.writeInt(mShutdownTimeoutMillis);
+        dest.writeBoolean(mClientControlByUser);
+        dest.writeTypedList(mBlockedClientList);
+        dest.writeTypedList(mAllowedClientList);
     }
 
     @Override
@@ -299,7 +333,9 @@
                     in.readString(),
                     in.readParcelable(MacAddress.class.getClassLoader()),
                     in.readString(), in.readBoolean(), in.readInt(), in.readInt(), in.readInt(),
-                    in.readInt(), in.readInt());
+                    in.readInt(), in.readInt(), in.readBoolean(),
+                    in.createTypedArrayList(MacAddress.CREATOR),
+                    in.createTypedArrayList(MacAddress.CREATOR));
         }
 
         @Override
@@ -387,6 +423,34 @@
     }
 
     /**
+     * Returns a flag indicating whether clients need to be pre-approved by the user.
+     * (true: authorization required) or not (false: not required).
+     * {@link Builder#enableClientControlByUser(Boolean)}.
+     */
+    public boolean isClientControlByUserEnabled() {
+        return mClientControlByUser;
+    }
+
+    /**
+     * Returns List of clients which aren't allowed to associate to the AP.
+     *
+     * Clients are configured using {@link Builder#setClientList(List, List)}
+     */
+    @NonNull
+    public List<MacAddress> getBlockedClientList() {
+        return mBlockedClientList;
+    }
+
+    /**
+     * List of clients which are allowed to associate to the AP.
+     * Clients are configured using {@link Builder#setClientList(List, List)}
+     */
+    @NonNull
+    public List<MacAddress> getAllowedClientList() {
+        return mAllowedClientList;
+    }
+
+    /**
      * Builds a {@link SoftApConfiguration}, which allows an app to configure various aspects of a
      * Soft AP.
      *
@@ -403,6 +467,9 @@
         private int mMaxNumberOfClients;
         private int mSecurityType;
         private int mShutdownTimeoutMillis;
+        private boolean mClientControlByUser;
+        private List<MacAddress> mBlockedClientList;
+        private List<MacAddress> mAllowedClientList;
 
         /**
          * Constructs a Builder with default values (see {@link Builder}).
@@ -417,6 +484,9 @@
             mMaxNumberOfClients = 0;
             mSecurityType = SECURITY_TYPE_OPEN;
             mShutdownTimeoutMillis = 0;
+            mClientControlByUser = false;
+            mBlockedClientList = new ArrayList<>();
+            mAllowedClientList = new ArrayList<>();
         }
 
         /**
@@ -434,6 +504,9 @@
             mMaxNumberOfClients = other.mMaxNumberOfClients;
             mSecurityType = other.mSecurityType;
             mShutdownTimeoutMillis = other.mShutdownTimeoutMillis;
+            mClientControlByUser = other.mClientControlByUser;
+            mBlockedClientList = new ArrayList<>(other.mBlockedClientList);
+            mAllowedClientList = new ArrayList<>(other.mAllowedClientList);
         }
 
         /**
@@ -445,7 +518,8 @@
         public SoftApConfiguration build() {
             return new SoftApConfiguration(mSsid, mBssid, mPassphrase,
                     mHiddenSsid, mBand, mChannel, mSecurityType, mMaxNumberOfClients,
-                    mShutdownTimeoutMillis);
+                    mShutdownTimeoutMillis, mClientControlByUser, mBlockedClientList,
+                    mAllowedClientList);
         }
 
         /**
@@ -662,5 +736,82 @@
             mShutdownTimeoutMillis = timeoutMillis;
             return this;
         }
+
+        /**
+         * Configure the Soft AP to require manual user control of client association.
+         * If disabled (the default) then any client can associate to this Soft AP using the
+         * correct credentials until the Soft AP capacity is reached (capacity is hardware, carrier,
+         * or user limited - using {@link #setMaxNumberOfClients(int)}).
+         *
+         * If manual user control is enabled then clients will be accepted, rejected, or require
+         * a user approval based on the configuration provided by
+         * {@link #setClientList(List, List)}.
+         *
+         * <p>
+         * This method requires hardware support. Hardware support can be determined using
+         * {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)} and
+         * {@link SoftApCapability#isFeatureSupported(int)}
+         * with {@link SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT}
+         *
+         * <p>
+         * If the method is called on a device without hardware support then starting the soft AP
+         * using {@link WifiManager#startTetheredHotspot(SoftApConfiguration)} will fail with
+         * {@link WifiManager#SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}.
+         *
+         * <p>
+         * <li>If not set, defaults to false (i.e The authoriztion is not required).</li>
+         *
+         * @param enabled true for enabling the control by user, false otherwise.
+         * @return Builder for chaining.
+         */
+        @NonNull
+        public Builder enableClientControlByUser(boolean enabled) {
+            mClientControlByUser = enabled;
+            return this;
+        }
+
+
+        /**
+         * This method together with {@link enableClientControlByUser(boolean)} control client
+         * connections to the AP. If {@link enableClientControlByUser(false)} is configured than
+         * this API has no effect and clients are allowed to associate to the AP (within limit of
+         * max number of clients).
+         *
+         * If {@link enableClientControlByUser(true)} is configured then this API configures
+         * 2 lists:
+         * <ul>
+         * <li>List of clients which are blocked. These are rejected.</li>
+         * <li>List of clients which are explicitly allowed. These are auto-accepted.</li>
+         * </ul>
+         *
+         * <p>
+         * All other clients which attempt to associate, whose MAC addresses are on neither list,
+         * are:
+         * <ul>
+         * <li>Rejected</li>
+         * <li>A callback {@link WifiManager.SoftApCallback#onBlockedClientConnecting(WifiClient)}
+         * is issued (which allows the user to add them to the allowed client list if desired).<li>
+         * </ul>
+         *
+         * @param blockedClientList list of clients which are not allowed to associate to the AP.
+         * @param allowedClientList list of clients which are allowed to associate to the AP
+         *                          without user pre-approval.
+         * @return Builder for chaining.
+         */
+        @NonNull
+        public Builder setClientList(@NonNull List<MacAddress> blockedClientList,
+                @NonNull List<MacAddress> allowedClientList) {
+            mBlockedClientList = new ArrayList<>(blockedClientList);
+            mAllowedClientList = new ArrayList<>(allowedClientList);
+            Iterator<MacAddress> iterator = mAllowedClientList.iterator();
+            while (iterator.hasNext()) {
+                MacAddress client = iterator.next();
+                int index = mBlockedClientList.indexOf(client);
+                if (index != -1) {
+                    throw new IllegalArgumentException("A MacAddress exist in both list");
+                }
+            }
+            return this;
+        }
     }
 }
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 1baab12..729ac14 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -666,7 +666,8 @@
     public @interface SapStartFailure {}
 
     /**
-     *  All other reasons for AP start failure besides {@link #SAP_START_FAILURE_NO_CHANNEL}.
+     *  All other reasons for AP start failure besides {@link #SAP_START_FAILURE_NO_CHANNEL} and
+     *  {@link #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}.
      *
      *  @hide
      */
@@ -691,6 +692,37 @@
     @SystemApi
     public static final int SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION = 2;
 
+
+    /** @hide */
+    @IntDef(flag = false, prefix = { "SAP_CLIENT_BLOCKED_REASON_" }, value = {
+        SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER,
+        SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SapClientBlockedReason {}
+
+    /**
+     *  If Soft Ap client is blocked, this reason code means that client doesn't exist in the
+     *  specified configuration {@link SoftApConfiguration.Builder#setClientList(List, List)}
+     *  and the {@link SoftApConfiguration.Builder#enableClientControlByUser(true)}
+     *  is configured as well.
+     *  @hide
+     */
+    @SystemApi
+    public static final int SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER = 0;
+
+    /**
+     *  If Soft Ap client is blocked, this reason code means that no more clients can be
+     *  associated to this AP since it reached maximum capacity. The maximum capacity is
+     *  the minimum of {@link SoftApConfiguration.Builder#setMaxNumberOfClients(int)} and
+     *  {@link SoftApCapability#getMaxSupportedClients} which get from
+     *  {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)}.
+     *
+     *  @hide
+     */
+    @SystemApi
+    public static final int SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS = 1;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"IFACE_IP_MODE_"}, value = {
@@ -3229,8 +3261,17 @@
     /**
      * Sets the Wi-Fi AP Configuration.
      *
+     * If the API is called while the soft AP is enabled, the configuration will apply to
+     * the current soft AP if the new configuration only includes
+     * {@link SoftApConfiguration.Builder#setMaxNumberOfClients(int)}
+     * or {@link SoftApConfiguration.Builder#setShutdownTimeoutMillis(int)}
+     * or {@link SoftApConfiguration.Builder#enableClientControlByUser(boolean)}
+     * or {@link SoftApConfiguration.Builder#setClientList(List, List)}.
+     *
+     * Otherwise, the configuration changes will be applied when the Soft AP is next started
+     * (the framework will not stop/start the AP).
+     *
      * @param softApConfig  A valid SoftApConfiguration specifying the configuration of the SAP.
-
      * @return {@code true} if the operation succeeded, {@code false} otherwise
      *
      * @hide
@@ -3460,7 +3501,8 @@
          *                      {@link #WIFI_AP_STATE_ENABLING}, {@link #WIFI_AP_STATE_FAILED}
          * @param failureReason reason when in failed state. One of
          *                      {@link #SAP_START_FAILURE_GENERAL},
-         *                      {@link #SAP_START_FAILURE_NO_CHANNEL}
+         *                      {@link #SAP_START_FAILURE_NO_CHANNEL},
+         *                      {@link #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}
          */
         default void onStateChanged(@WifiApState int state, @SapStartFailure int failureReason) {}
 
@@ -3489,6 +3531,22 @@
             // Do nothing: can be updated to add SoftApCapability details (e.g. meximum supported
             // client number) to the UI.
         }
+
+        /**
+         * Called when client trying to connect but device blocked the client with specific reason.
+         *
+         * Can be used to ask user to update client to allowed list or blocked list
+         * when reason is {@link SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER}, or
+         * indicate the block due to maximum supported client number limitation when reason is
+         * {@link SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS}.
+         *
+         * @param client the currently blocked client.
+         * @param blockedReason one of blocked reason from {@link SapClientBlockedReason}
+         */
+        default void onBlockedClientConnecting(@NonNull WifiClient client,
+                @SapClientBlockedReason int blockedReason) {
+            // Do nothing: can be used to ask user to update client to allowed list or blocked list.
+        }
     }
 
     /**
@@ -3555,6 +3613,19 @@
                 mCallback.onCapabilityChanged(capability);
             });
         }
+
+        @Override
+        public void onBlockedClientConnecting(@NonNull WifiClient client, int blockedReason) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "SoftApCallbackProxy: onBlockedClientConnecting: client=" + client
+                        + " with reason = " + blockedReason);
+            }
+
+            Binder.clearCallingIdentity();
+            mExecutor.execute(() -> {
+                mCallback.onBlockedClientConnecting(client, blockedReason);
+            });
+        }
     }
 
     /**
diff --git a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
index eeea7e2..6884a4e 100644
--- a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
@@ -25,6 +25,8 @@
 
 import org.junit.Test;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Random;
 
 @SmallTest
@@ -112,12 +114,18 @@
 
     @Test
     public void testWpa2WithAllFieldCustomized() {
+        List<MacAddress> testBlockedClientList = new ArrayList<>();
+        List<MacAddress> testAllowedClientList = new ArrayList<>();
+        testBlockedClientList.add(MacAddress.fromString("11:22:33:44:55:66"));
+        testAllowedClientList.add(MacAddress.fromString("aa:bb:cc:dd:ee:ff"));
         SoftApConfiguration original = new SoftApConfiguration.Builder()
                 .setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
                 .setChannel(149, SoftApConfiguration.BAND_5GHZ)
                 .setHiddenSsid(true)
                 .setMaxNumberOfClients(10)
                 .setShutdownTimeoutMillis(500000)
+                .enableClientControlByUser(true)
+                .setClientList(testBlockedClientList, testAllowedClientList)
                 .build();
         assertThat(original.getPassphrase()).isEqualTo("secretsecret");
         assertThat(original.getSecurityType()).isEqualTo(
@@ -127,6 +135,9 @@
         assertThat(original.isHiddenSsid()).isEqualTo(true);
         assertThat(original.getMaxNumberOfClients()).isEqualTo(10);
         assertThat(original.getShutdownTimeoutMillis()).isEqualTo(500000);
+        assertThat(original.isClientControlByUserEnabled()).isEqualTo(true);
+        assertThat(original.getBlockedClientList()).isEqualTo(testBlockedClientList);
+        assertThat(original.getAllowedClientList()).isEqualTo(testAllowedClientList);
 
         SoftApConfiguration unparceled = parcelUnparcel(original);
         assertThat(unparceled).isNotSameAs(original);
@@ -238,4 +249,17 @@
                 .setShutdownTimeoutMillis(-1)
                 .build();
     }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testsetClientListExceptionWhenExistMacAddressInBothList() {
+        final MacAddress testMacAddress_1 = MacAddress.fromString("22:33:44:55:66:77");
+        final MacAddress testMacAddress_2 = MacAddress.fromString("aa:bb:cc:dd:ee:ff");
+        ArrayList<MacAddress> testAllowedClientList = new ArrayList<>();
+        testAllowedClientList.add(testMacAddress_1);
+        testAllowedClientList.add(testMacAddress_2);
+        ArrayList<MacAddress> testBlockedClientList = new ArrayList<>();
+        testBlockedClientList.add(testMacAddress_1);
+        SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
+        configBuilder.setClientList(testBlockedClientList, testAllowedClientList);
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 4b83718..5bdc344 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -70,6 +70,7 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.net.DhcpInfo;
+import android.net.MacAddress;
 import android.net.wifi.WifiManager.LocalOnlyHotspotCallback;
 import android.net.wifi.WifiManager.LocalOnlyHotspotObserver;
 import android.net.wifi.WifiManager.LocalOnlyHotspotReservation;
@@ -897,6 +898,25 @@
     }
 
     /*
+     * Verify client-provided callback is being called through callback proxy
+     */
+    @Test
+    public void softApCallbackProxyCallsOnBlockedClientConnecting() throws Exception {
+        WifiClient testWifiClient = new WifiClient(MacAddress.fromString("22:33:44:55:66:77"));
+        ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor =
+                ArgumentCaptor.forClass(ISoftApCallback.Stub.class);
+        mWifiManager.registerSoftApCallback(new HandlerExecutor(mHandler), mSoftApCallback);
+        verify(mWifiService).registerSoftApCallback(any(IBinder.class), callbackCaptor.capture(),
+                anyInt());
+
+        callbackCaptor.getValue().onBlockedClientConnecting(testWifiClient,
+                WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS);
+        mLooper.dispatchAll();
+        verify(mSoftApCallback).onBlockedClientConnecting(testWifiClient,
+                WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS);
+    }
+
+    /*
      * Verify client-provided callback is being called through callback proxy on multiple events
      */
     @Test