1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.wifi.hotspot2; 18 19 import android.app.AlarmManager; 20 import android.os.Handler; 21 import android.util.Log; 22 23 import com.android.internal.annotations.VisibleForTesting; 24 import com.android.server.wifi.Clock; 25 import com.android.server.wifi.WifiInjector; 26 import com.android.server.wifi.hotspot2.anqp.Constants; 27 import com.android.wifi.flags.FeatureFlags; 28 29 import java.io.PrintWriter; 30 import java.util.ArrayDeque; 31 import java.util.ArrayList; 32 import java.util.Arrays; 33 import java.util.HashMap; 34 import java.util.List; 35 import java.util.Map; 36 import java.util.Queue; 37 38 /** 39 * Class for managing sending of ANQP requests. This manager will ignore ANQP requests for a 40 * period of time (hold off time) to a specified AP if the previous request to that AP goes 41 * unanswered or failed. The hold off time will increase exponentially until the max is reached. 42 */ 43 public class ANQPRequestManager { 44 private static final int ANQP_REQUEST_ALARM_INTERVAL_MS = 2_000; 45 @VisibleForTesting 46 public static final String ANQP_REQUEST_ALARM_TAG = "anqpRequestAlarm"; 47 private static final String TAG = "ANQPRequestManager"; 48 49 private final PasspointEventHandler mPasspointHandler; 50 private final AlarmManager mAlarmManager; 51 private final Clock mClock; 52 private final FeatureFlags mFeatureFlags; 53 private boolean mAnqpRequestPending; 54 55 /** 56 * List of pending ANQP request associated with an AP (BSSID). 57 */ 58 private final Map<Long, ANQPNetworkKey> mPendingQueries; 59 private final Queue<AnqpRequest> mPendingRequest = new ArrayDeque<>(); 60 61 /** 62 * List of hold off time information associated with APs specified by their BSSID. 63 * Used to determine when an ANQP request can be send to the corresponding AP after the 64 * previous request goes unanswered or failed. 65 */ 66 private final Map<Long, HoldOffInfo> mHoldOffInfo; 67 68 /** 69 * Minimum number of milliseconds to wait for before attempting ANQP queries to the same AP 70 * after previous request goes unanswered or failed. 71 */ 72 @VisibleForTesting 73 public static final int BASE_HOLDOFF_TIME_MILLISECONDS = 10000; 74 75 /** 76 * Max value for the hold off counter for unanswered/failed queries. This limits the maximum 77 * hold off time to: 78 * BASE_HOLDOFF_TIME_MILLISECONDS * 2^MAX_HOLDOFF_COUNT 79 * which is 640 seconds. 80 */ 81 @VisibleForTesting 82 public static final int MAX_HOLDOFF_COUNT = 6; 83 84 private static final List<Constants.ANQPElementType> R1_ANQP_BASE_SET = Arrays.asList( 85 Constants.ANQPElementType.ANQPVenueName, 86 Constants.ANQPElementType.ANQPIPAddrAvailability, 87 Constants.ANQPElementType.ANQPNAIRealm, 88 Constants.ANQPElementType.ANQP3GPPNetwork, 89 Constants.ANQPElementType.ANQPDomName, 90 Constants.ANQPElementType.HSFriendlyName, 91 Constants.ANQPElementType.HSWANMetrics, 92 Constants.ANQPElementType.HSConnCapability); 93 94 private static final List<Constants.ANQPElementType> R2_ANQP_BASE_SET = Arrays.asList( 95 Constants.ANQPElementType.HSOSUProviders); 96 private final Handler mHandler; 97 98 /** 99 * Class to keep track of AP status for ANQP requests. 100 */ 101 private class HoldOffInfo { 102 /** 103 * Current hold off count. Will max out at {@link #MAX_HOLDOFF_COUNT}. 104 */ 105 public int holdOffCount; 106 /** 107 * The time stamp in milliseconds when we're allow to send ANQP request to the 108 * corresponding AP. 109 */ 110 public long holdOffExpirationTime; 111 } 112 private static class AnqpRequest { 113 AnqpRequest(long bssid, boolean rcOIs, NetworkDetail.HSRelease hsRelease, ANQPNetworkKey anqpNetworkKey)114 AnqpRequest(long bssid, boolean rcOIs, NetworkDetail.HSRelease hsRelease, 115 ANQPNetworkKey anqpNetworkKey) { 116 mBssid = bssid; 117 mAnqpNetworkKey = anqpNetworkKey; 118 mRcOIs = rcOIs; 119 mHsRelease = hsRelease; 120 } 121 public final long mBssid; 122 public final boolean mRcOIs; 123 public final NetworkDetail.HSRelease mHsRelease; 124 public final ANQPNetworkKey mAnqpNetworkKey; 125 } 126 127 private final AlarmManager.OnAlarmListener mAnqpRequestListener = 128 new AlarmManager.OnAlarmListener() { 129 public void onAlarm() { 130 mAnqpRequestPending = false; 131 processNextRequest(); 132 } 133 }; 134 ANQPRequestManager(PasspointEventHandler passpointEventHandler, Clock clock, WifiInjector wifiInjector, Handler handler)135 public ANQPRequestManager(PasspointEventHandler passpointEventHandler, Clock clock, 136 WifiInjector wifiInjector, Handler handler) { 137 mPasspointHandler = passpointEventHandler; 138 mClock = clock; 139 mAlarmManager = wifiInjector.getAlarmManager(); 140 mFeatureFlags = wifiInjector.getDeviceConfigFacade().getFeatureFlags(); 141 mHoldOffInfo = new HashMap<>(); 142 mPendingQueries = new HashMap<>(); 143 mHandler = handler; 144 } 145 146 /** 147 * Request ANQP elements from the specified AP. This will request the basic Release 1 ANQP 148 * elements {@link #R1_ANQP_BASE_SET}. Additional elements will be requested based on the 149 * information provided in the Information Element (Roaming Consortium OI count and the 150 * supported Hotspot 2.0 release version). 151 * 152 * @param bssid The BSSID of the AP 153 * @param anqpNetworkKey The unique network key associated with this request 154 * @param rcOIs Flag indicating the inclusion of roaming consortium OIs. When set to true, 155 * Roaming Consortium ANQP element will be requested 156 * @param hsReleaseVer Indicates Hotspot 2.0 Release version. When set to R2 or higher, 157 * the Release 2 ANQP elements {@link #R2_ANQP_BASE_SET} will be requested 158 */ requestANQPElements(long bssid, ANQPNetworkKey anqpNetworkKey, boolean rcOIs, NetworkDetail.HSRelease hsReleaseVer)159 public void requestANQPElements(long bssid, ANQPNetworkKey anqpNetworkKey, boolean rcOIs, 160 NetworkDetail.HSRelease hsReleaseVer) { 161 // Put the new request in the queue, process it if possible(no more pending request) 162 mPendingRequest.offer(new AnqpRequest(bssid, rcOIs, hsReleaseVer, anqpNetworkKey)); 163 processNextRequest(); 164 } 165 processNextRequest()166 private void processNextRequest() { 167 if (mAnqpRequestPending) { 168 return; 169 } 170 AnqpRequest request; 171 while ((request = mPendingRequest.poll()) != null) { 172 // Check if we are allow to send the request now. 173 if (!canSendRequestNow(request.mBssid)) { 174 continue; 175 } 176 // No need to hold off future requests and set next alarm for send failures. 177 if (mPasspointHandler.requestANQP(request.mBssid, getRequestElementIDs(request.mRcOIs, 178 request.mHsRelease))) { 179 break; 180 } 181 } 182 if (request == null) { 183 return; 184 } 185 // Update hold off info on when we are allowed to send the next ANQP request to 186 // the given AP. 187 updateHoldOffInfo(request.mBssid); 188 mPendingQueries.put(request.mBssid, request.mAnqpNetworkKey); 189 // Schedule next request in case of time out waiting for response. 190 mAlarmManager.set( 191 AlarmManager.ELAPSED_REALTIME, 192 mClock.getElapsedSinceBootMillis() + ANQP_REQUEST_ALARM_INTERVAL_MS, 193 ANQP_REQUEST_ALARM_TAG, 194 mAnqpRequestListener, 195 mHandler); 196 mAnqpRequestPending = true; 197 } 198 199 /** 200 * Request Venue URL ANQP-element from the specified AP post connection. 201 * 202 * @param bssid The BSSID of the AP 203 * @param anqpNetworkKey The unique network key associated with this request 204 * @return true if a request was sent successfully 205 */ requestVenueUrlAnqpElement(long bssid, ANQPNetworkKey anqpNetworkKey)206 public boolean requestVenueUrlAnqpElement(long bssid, ANQPNetworkKey anqpNetworkKey) { 207 if (!mPasspointHandler.requestVenueUrlAnqp(bssid)) { 208 return false; 209 } 210 211 return true; 212 } 213 214 /** 215 * Notification of the completion of an ANQP request. 216 * 217 * @param bssid The BSSID of the AP 218 * @param success Flag indicating the result of the query 219 * @return {@link ANQPNetworkKey} associated with the completed request 220 */ onRequestCompleted(long bssid, boolean success)221 public ANQPNetworkKey onRequestCompleted(long bssid, boolean success) { 222 if (success) { 223 // Query succeeded. No need to hold off request to the given AP. 224 mHoldOffInfo.remove(bssid); 225 } 226 // Cancel the schedule, and process next request. 227 mAlarmManager.cancel(mAnqpRequestListener); 228 mAnqpRequestPending = false; 229 processNextRequest(); 230 return mPendingQueries.remove(bssid); 231 } 232 233 /** 234 * Check if we are allowed to send ANQP request to the specified AP now. 235 * 236 * @param bssid The BSSID of an AP 237 * @return true if we are allowed to send the request now 238 */ canSendRequestNow(long bssid)239 private boolean canSendRequestNow(long bssid) { 240 long currentTime = mClock.getElapsedSinceBootMillis(); 241 HoldOffInfo info = mHoldOffInfo.get(bssid); 242 if (info != null && info.holdOffExpirationTime > currentTime) { 243 Log.d(TAG, "Not allowed to send ANQP request to " + Utils.macToString(bssid) 244 + " for another " + (info.holdOffExpirationTime - currentTime) / 1000 245 + " seconds"); 246 return false; 247 } 248 249 return true; 250 } 251 252 /** 253 * Update the ANQP request hold off info associated with the given AP. 254 * 255 * @param bssid The BSSID of an AP 256 */ updateHoldOffInfo(long bssid)257 private void updateHoldOffInfo(long bssid) { 258 HoldOffInfo info = mHoldOffInfo.get(bssid); 259 if (info == null) { 260 info = new HoldOffInfo(); 261 mHoldOffInfo.put(bssid, info); 262 } 263 info.holdOffExpirationTime = mClock.getElapsedSinceBootMillis() 264 + BASE_HOLDOFF_TIME_MILLISECONDS * (1 << info.holdOffCount); 265 if (info.holdOffCount < MAX_HOLDOFF_COUNT) { 266 info.holdOffCount++; 267 } 268 } 269 270 /** 271 * Get the list of ANQP element IDs to request based on the Hotspot 2.0 release number 272 * and the ANQP OI count indicated in the Information Element. 273 * 274 * @param rcOIs Flag indicating the inclusion of roaming consortium OIs 275 * @param hsRelease Hotspot 2.0 Release version of the AP 276 * @return List of ANQP Element ID 277 */ getRequestElementIDs(boolean rcOIs, NetworkDetail.HSRelease hsRelease)278 private static List<Constants.ANQPElementType> getRequestElementIDs(boolean rcOIs, 279 NetworkDetail.HSRelease hsRelease) { 280 List<Constants.ANQPElementType> requestList = new ArrayList<>(); 281 requestList.addAll(R1_ANQP_BASE_SET); 282 if (rcOIs) { 283 requestList.add(Constants.ANQPElementType.ANQPRoamingConsortium); 284 } 285 286 if (hsRelease == NetworkDetail.HSRelease.R1) { 287 // Return R1 ANQP request list 288 return requestList; 289 } 290 291 requestList.addAll(R2_ANQP_BASE_SET); 292 293 // Return R2+ ANQP request list. This also includes the Unknown version, which may imply 294 // a future version. 295 return requestList; 296 } 297 298 /** 299 * Dump the current state of ANQPRequestManager to the provided output stream. 300 * 301 * @param pw The output stream to write to 302 */ dump(PrintWriter pw)303 public void dump(PrintWriter pw) { 304 pw.println("ANQPRequestManager - Begin ---"); 305 for (Map.Entry<Long, HoldOffInfo> holdOffInfo : mHoldOffInfo.entrySet()) { 306 long bssid = holdOffInfo.getKey(); 307 pw.println("For BBSID: " + Utils.macToString(bssid)); 308 pw.println("holdOffCount: " + holdOffInfo.getValue().holdOffCount); 309 pw.println("Not allowed to send ANQP request for another " 310 + (holdOffInfo.getValue().holdOffExpirationTime 311 - mClock.getElapsedSinceBootMillis()) / 1000 + " seconds"); 312 } 313 pw.println("ANQPRequestManager - End ---"); 314 } 315 316 /** 317 * Clear all pending ANQP requests 318 */ clear()319 public void clear() { 320 mPendingQueries.clear(); 321 mHoldOffInfo.clear(); 322 mAlarmManager.cancel(mAnqpRequestListener); 323 mAnqpRequestPending = false; 324 mPendingRequest.clear(); 325 } 326 } 327