ANQPRequestManager.java revision ec28f863c5e46c0a75e8bdb92283304b875ee0f2
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
17package com.android.server.wifi.hotspot2;
18
19import android.util.Log;
20
21import com.android.internal.annotations.VisibleForTesting;
22import com.android.server.wifi.Clock;
23import com.android.server.wifi.hotspot2.anqp.Constants;
24
25import java.util.ArrayList;
26import java.util.Arrays;
27import java.util.HashMap;
28import java.util.List;
29import java.util.Map;
30
31/**
32 * Class for managing sending of ANQP requests.  This manager will ignore ANQP requests for a
33 * period of time (hold off time) to a specified AP if the previous request to that AP goes
34 * unanswered or failed.  The hold off time will increase exponentially until the max is reached.
35 */
36public class ANQPRequestManager {
37    private static final String TAG = "ANQPRequestManager";
38
39    private final PasspointEventHandler mPasspointHandler;
40    private final Clock mClock;
41
42    /**
43     * List of pending ANQP request associated with an AP (BSSID).
44     */
45    private final Map<Long, ANQPNetworkKey> mPendingQueries;
46
47    /**
48     * List of hold off time information associated with APs specified by their BSSID.
49     * Used to determine when an ANQP request can be send to the corresponding AP after the
50     * previous request goes unanswered or failed.
51     */
52    private final Map<Long, HoldOffInfo> mHoldOffInfo;
53
54    /**
55     * Minimum number of milliseconds to wait for before attempting ANQP queries to the same AP
56     * after previous request goes unanswered or failed.
57     */
58    @VisibleForTesting
59    public static final int BASE_HOLDOFF_TIME_MILLISECONDS = 10000;
60
61    /**
62     * Max value for the hold off counter for unanswered/failed queries.  This limits the maximum
63     * hold off time to:
64     * BASE_HOLDOFF_TIME_MILLISECONDS * 2^MAX_HOLDOFF_COUNT
65     * which is 640 seconds.
66     */
67    @VisibleForTesting
68    public static final int MAX_HOLDOFF_COUNT = 6;
69
70    private static final List<Constants.ANQPElementType> R1_ANQP_BASE_SET = Arrays.asList(
71            Constants.ANQPElementType.ANQPVenueName,
72            Constants.ANQPElementType.ANQPIPAddrAvailability,
73            Constants.ANQPElementType.ANQPNAIRealm,
74            Constants.ANQPElementType.ANQP3GPPNetwork,
75            Constants.ANQPElementType.ANQPDomName);
76
77    private static final List<Constants.ANQPElementType> R2_ANQP_BASE_SET = Arrays.asList(
78            Constants.ANQPElementType.HSFriendlyName,
79            Constants.ANQPElementType.HSWANMetrics,
80            Constants.ANQPElementType.HSConnCapability);
81
82    /**
83     * Class to keep track of AP status for ANQP requests.
84     */
85    private class HoldOffInfo {
86        /**
87         * Current hold off count.  Will max out at {@link #MAX_HOLDOFF_COUNT}.
88         */
89        public int holdOffCount;
90        /**
91         * The time stamp in milliseconds when we're allow to send ANQP request to the
92         * corresponding AP.
93         */
94        public long holdOffExpirationTime;
95    }
96
97    public ANQPRequestManager(PasspointEventHandler handler, Clock clock) {
98        mPasspointHandler = handler;
99        mClock = clock;
100        mPendingQueries = new HashMap<>();
101        mHoldOffInfo = new HashMap<>();
102    }
103
104    /**
105     * Request ANQP elements from the specified AP.  This will request the basic Release 1 ANQP
106     * elements {@link #R1_ANQP_BASE_SET}.  Additional elements will be requested based on the
107     * information provided in the Information Element (Roaming Consortium OI count and the
108     * supported Hotspot 2.0 release version).
109     *
110     * @param bssid The BSSID of the AP
111     * @param anqpNetworkKey The unique network key associated with this request
112     * @param rcOIs Flag indicating the inclusion of roaming consortium OIs. When set to true,
113     *              Roaming Consortium ANQP element will be requested
114     * @param hsReleaseR2 Flag indicating the support of Hotspot 2.0 Release 2. When set to true,
115     *              the Release 2 ANQP elements {@link #R2_ANQP_BASE_SET} will be requested
116     * @return true if a request was sent successfully
117     */
118    public boolean requestANQPElements(long bssid, ANQPNetworkKey anqpNetworkKey, boolean rcOIs,
119            boolean hsReleaseR2) {
120        // Check if we are allow to send the request now.
121        if (!canSendRequestNow(bssid)) {
122            return false;
123        }
124
125        // No need to hold off future requests for send failures.
126        if (!mPasspointHandler.requestANQP(bssid, getRequestElementIDs(rcOIs, hsReleaseR2))) {
127            return false;
128        }
129
130        // Update hold off info on when we are allowed to send the next ANQP request to
131        // the given AP.
132        updateHoldOffInfo(bssid);
133
134        mPendingQueries.put(bssid, anqpNetworkKey);
135        return true;
136    }
137
138    /**
139     * Notification of the completion of an ANQP request.
140     *
141     * @param bssid The BSSID of the AP
142     * @param success Flag indicating the result of the query
143     * @return {@link ANQPNetworkKey} associated with the completed request
144     */
145    public ANQPNetworkKey onRequestCompleted(long bssid, boolean success) {
146        if (success) {
147            // Query succeeded.  No need to hold off request to the given AP.
148            mHoldOffInfo.remove(bssid);
149        }
150        return mPendingQueries.remove(bssid);
151    }
152
153    /**
154     * Check if we are allowed to send ANQP request to the specified AP now.
155     *
156     * @param bssid The BSSID of an AP
157     * @return true if we are allowed to send the request now
158     */
159    private boolean canSendRequestNow(long bssid) {
160        long currentTime = mClock.getElapsedSinceBootMillis();
161        HoldOffInfo info = mHoldOffInfo.get(bssid);
162        if (info != null && info.holdOffExpirationTime > currentTime) {
163            Log.d(TAG, "Not allowed to send ANQP request to " + bssid + " for another "
164                    + (info.holdOffExpirationTime - currentTime) / 1000 + " seconds");
165            return false;
166        }
167
168        return true;
169    }
170
171    /**
172     * Update the ANQP request hold off info associated with the given AP.
173     *
174     * @param bssid The BSSID of an AP
175     */
176    private void updateHoldOffInfo(long bssid) {
177        HoldOffInfo info = mHoldOffInfo.get(bssid);
178        if (info == null) {
179            info = new HoldOffInfo();
180            mHoldOffInfo.put(bssid, info);
181        }
182        info.holdOffExpirationTime = mClock.getElapsedSinceBootMillis()
183                + BASE_HOLDOFF_TIME_MILLISECONDS * (1 << info.holdOffCount);
184        if (info.holdOffCount < MAX_HOLDOFF_COUNT) {
185            info.holdOffCount++;
186        }
187    }
188
189    /**
190     * Get the list of ANQP element IDs to request based on the Hotspot 2.0 release number
191     * and the ANQP OI count indicated in the Information Element.
192     *
193     * @param rcOIs Flag indicating the inclusion of roaming consortium OIs
194     * @param hsReleaseR2 Flag indicating support of Hotspot 2.0 Release 2
195     * @return List of ANQP Element ID
196     */
197    private static List<Constants.ANQPElementType> getRequestElementIDs(boolean rcOIs,
198            boolean hsReleaseR2) {
199        List<Constants.ANQPElementType> requestList = new ArrayList<>();
200        requestList.addAll(R1_ANQP_BASE_SET);
201        if (rcOIs) {
202            requestList.add(Constants.ANQPElementType.ANQPRoamingConsortium);
203        }
204
205        if (hsReleaseR2) {
206            requestList.addAll(R2_ANQP_BASE_SET);
207        }
208        return requestList;
209    }
210}
211