• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.content.Context;
23 import android.net.wifi.IListListener;
24 import android.net.wifi.QosPolicyParams;
25 import android.net.wifi.WifiManager;
26 import android.os.Handler;
27 import android.os.HandlerThread;
28 import android.os.IBinder;
29 import android.os.RemoteException;
30 import android.util.Log;
31 
32 import com.android.internal.annotations.VisibleForTesting;
33 import com.android.wifi.resources.R;
34 
35 import java.io.PrintWriter;
36 import java.lang.annotation.Retention;
37 import java.lang.annotation.RetentionPolicy;
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.HashMap;
41 import java.util.List;
42 import java.util.Map;
43 
44 /**
45  * Handler for QoS policy requests initiated by applications.
46  */
47 public class ApplicationQosPolicyRequestHandler {
48     private static final String TAG = "ApplicationQosPolicyRequestHandler";
49 
50     // QosPolicyParams objects contain an integer policyId in the range [1, 255],
51     // while the HAL expects a byte policyId in the range [-128, 127].
52     private static final int HAL_POLICY_ID_MIN = Byte.MIN_VALUE;
53     private static final int HAL_POLICY_ID_MAX = Byte.MAX_VALUE;
54     private static final int MAX_POLICIES_PER_TRANSACTION =
55             WifiManager.getMaxNumberOfPoliciesPerQosRequest();
56     private static final int DEFAULT_UID = -1;
57 
58     // HAL should automatically time out at 1000 ms. Perform a local check at 1500 ms to verify
59     // that either the expected callback, or the timeout callback, was received.
60     @VisibleForTesting
61     protected static final int CALLBACK_TIMEOUT_MILLIS = 1500;
62 
63     private final ActiveModeWarden mActiveModeWarden;
64     private final WifiNative mWifiNative;
65     private final Handler mHandler;
66     private final ApCallback mApCallback;
67     private final ApplicationQosPolicyTrackingTable mPolicyTrackingTable;
68     private final ApplicationDeathRecipient mApplicationDeathRecipient;
69     private final DeviceConfigFacade mDeviceConfigFacade;
70     private final Context mContext;
71     private boolean mVerboseLoggingEnabled;
72 
73     private Map<String, List<QueuedRequest>> mPerIfaceRequestQueue;
74     private Map<String, CallbackParams> mPendingCallbacks;
75     private Map<IBinder, Integer> mApplicationBinderToUidMap;
76     private Map<Integer, IBinder> mApplicationUidToBinderMap;
77 
78     private static final int REQUEST_TYPE_ADD = 0;
79     private static final int REQUEST_TYPE_REMOVE = 1;
80 
81     @IntDef(prefix = { "REQUEST_TYPE_" }, value = {
82             REQUEST_TYPE_ADD,
83             REQUEST_TYPE_REMOVE,
84     })
85     @Retention(RetentionPolicy.SOURCE)
86     private @interface RequestType {}
87 
88     private static class QueuedRequest {
89         // Initial state.
90         public final @RequestType int requestType;
91         public final @Nullable List<QosPolicyParams> policiesToAdd;
92         public final @Nullable List<Integer> policyIdsToRemove;
93         public final @NonNull ApplicationCallback callback;
94         public final @Nullable IBinder binder;
95         public final int requesterUid;
96 
97         // Set during processing.
98         public boolean processedOnAnyIface;
99         public @Nullable List<Integer> initialStatusList;
100         public @Nullable List<Byte> virtualPolicyIdsToRemove;
101 
QueuedRequest(@equestType int inRequestType, @Nullable List<QosPolicyParams> inPoliciesToAdd, @Nullable List<Integer> inPolicyIdsToRemove, @Nullable IListListener inListener, @Nullable IBinder inBinder, int inRequesterUid)102         QueuedRequest(@RequestType int inRequestType,
103                 @Nullable List<QosPolicyParams> inPoliciesToAdd,
104                 @Nullable List<Integer> inPolicyIdsToRemove,
105                 @Nullable IListListener inListener, @Nullable IBinder inBinder,
106                 int inRequesterUid) {
107             requestType = inRequestType;
108             policiesToAdd = inPoliciesToAdd;
109             policyIdsToRemove = inPolicyIdsToRemove;
110             callback = new ApplicationCallback(inListener);
111             binder = inBinder;
112             requesterUid = inRequesterUid;
113             processedOnAnyIface = false;
114         }
115 
116         @Override
toString()117         public String toString() {
118             return "{requestType: " + requestType + ", "
119                     + "policiesToAdd: " + policiesToAdd + ", "
120                     + "policyIdsToRemove: " + policyIdsToRemove + ", "
121                     + "callback: " + callback + ", "
122                     + "binder: " + binder + ", "
123                     + "requesterUid: " + requesterUid + ", "
124                     + "processedOnAnyIface: " + processedOnAnyIface + ", "
125                     + "initialStatusList: " + initialStatusList + ", "
126                     + "virtualPolicyIdsToRemove: " + virtualPolicyIdsToRemove + "}";
127         }
128     }
129 
130     /**
131      * Wrapper around the calling application's IListListener.
132      * Ensures that the listener is only called once.
133      */
134     private static class ApplicationCallback {
135         private @Nullable IListListener mListener;
136 
ApplicationCallback(@ullable IListListener inListener)137         ApplicationCallback(@Nullable IListListener inListener) {
138             mListener = inListener;
139         }
140 
sendResult(List<Integer> statusList)141         public void sendResult(List<Integer> statusList) {
142             if (mListener == null) return;
143             try {
144                 mListener.onResult(statusList);
145             } catch (RemoteException e) {
146                 Log.e(TAG, "Listener received remote exception " + e);
147             }
148 
149             // Set mListener to null to avoid calling again.
150             // The application should only be notified once.
151             mListener = null;
152         }
153 
154         /**
155          * Use when all policies should be assigned the same status code.
156          * Ex. If all policies are rejected with the same error code.
157          */
sendResult(int size, @WifiManager.QosRequestStatus int statusCode)158         public void sendResult(int size, @WifiManager.QosRequestStatus int statusCode) {
159             List<Integer> statusList = new ArrayList<>();
160             for (int i = 0; i < size; i++) {
161                 statusList.add(statusCode);
162             }
163             sendResult(statusList);
164         }
165 
166         @Override
toString()167         public String toString() {
168             return mListener != null ? mListener.toString() : "null";
169         }
170     }
171 
172     /**
173      * Represents a request that has been sent to the HAL and is awaiting the AP callback.
174      */
175     private static class CallbackParams {
176         public final @NonNull List<Byte> policyIds;
177 
CallbackParams(@onNull List<Byte> inPolicyIds)178         CallbackParams(@NonNull List<Byte> inPolicyIds) {
179             Collections.sort(inPolicyIds);
180             policyIds = inPolicyIds;
181         }
182 
matchesResults(List<SupplicantStaIfaceHal.QosPolicyStatus> resultList)183         public boolean matchesResults(List<SupplicantStaIfaceHal.QosPolicyStatus> resultList) {
184             List<Byte> resultPolicyIds = new ArrayList<>();
185             for (SupplicantStaIfaceHal.QosPolicyStatus status : resultList) {
186                 resultPolicyIds.add((byte) status.policyId);
187             }
188             Collections.sort(resultPolicyIds);
189             return policyIds.equals(resultPolicyIds);
190         }
191 
192         @Override
toString()193         public String toString() {
194             return "{policyIds: " + policyIds + "}";
195         }
196     }
197 
198     private class ApCallback implements SupplicantStaIfaceHal.QosScsResponseCallback {
199         @Override
onApResponse(String ifaceName, List<SupplicantStaIfaceHal.QosPolicyStatus> halStatusList)200         public void onApResponse(String ifaceName,
201                 List<SupplicantStaIfaceHal.QosPolicyStatus> halStatusList) {
202             mHandler.post(() -> {
203                 logApCallbackMockable(ifaceName, halStatusList);
204                 CallbackParams expectedParams = mPendingCallbacks.get(ifaceName);
205                 if (expectedParams == null) {
206                     Log.i(TAG, "Callback was not expected on this interface");
207                     return;
208                 }
209 
210                 if (!expectedParams.matchesResults(halStatusList)) {
211                     // Silently ignore this callback if it does not match the expected parameters.
212                     Log.i(TAG, "Callback was unsolicited. statusList: " + halStatusList);
213                     return;
214                 }
215 
216                 Log.i(TAG, "Expected callback was received");
217                 mPendingCallbacks.remove(ifaceName);
218                 processNextRequestIfPossible(ifaceName);
219             });
220         }
221     }
222 
223     private class ApplicationDeathRecipient implements IBinder.DeathRecipient {
224         @Override
binderDied()225         public void binderDied() {
226         }
227 
228         @Override
binderDied(@onNull IBinder who)229         public void binderDied(@NonNull IBinder who) {
230             mHandler.post(() -> {
231                 Integer uid = mApplicationBinderToUidMap.get(who);
232                 Log.i(TAG, "Application binder died. who=" + who + ", uid=" + uid);
233                 if (uid == null) {
234                     // Application is not registered with us.
235                     return;
236                 }
237 
238                 // Remove this application from the tracking maps
239                 // and clear out any policies that they own.
240                 mApplicationBinderToUidMap.remove(who);
241                 mApplicationUidToBinderMap.remove(uid);
242                 queueRemoveAllRequest(uid);
243             });
244         }
245     }
246 
ApplicationQosPolicyRequestHandler(@onNull ActiveModeWarden activeModeWarden, @NonNull WifiNative wifiNative, @NonNull HandlerThread handlerThread, @NonNull DeviceConfigFacade deviceConfigFacade, @NonNull Context context)247     public ApplicationQosPolicyRequestHandler(@NonNull ActiveModeWarden activeModeWarden,
248             @NonNull WifiNative wifiNative, @NonNull HandlerThread handlerThread,
249             @NonNull DeviceConfigFacade deviceConfigFacade, @NonNull Context context) {
250         mActiveModeWarden = activeModeWarden;
251         mWifiNative = wifiNative;
252         mHandler = new Handler(handlerThread.getLooper());
253         mPerIfaceRequestQueue = new HashMap<>();
254         mPendingCallbacks = new HashMap<>();
255         mApplicationBinderToUidMap = new HashMap<>();
256         mApplicationUidToBinderMap = new HashMap<>();
257         mApCallback = new ApCallback();
258         mApplicationDeathRecipient = new ApplicationDeathRecipient();
259         mDeviceConfigFacade = deviceConfigFacade;
260         mContext = context;
261         mVerboseLoggingEnabled = false;
262         mPolicyTrackingTable =
263                 new ApplicationQosPolicyTrackingTable(HAL_POLICY_ID_MIN, HAL_POLICY_ID_MAX);
264         mWifiNative.registerQosScsResponseCallback(mApCallback);
265     }
266 
267     /**
268      * Enable or disable verbose logging.
269      */
enableVerboseLogging(boolean enable)270     public void enableVerboseLogging(boolean enable) {
271         mVerboseLoggingEnabled = enable;
272     }
273 
274     @VisibleForTesting
logApCallbackMockable(String ifaceName, List<SupplicantStaIfaceHal.QosPolicyStatus> halStatusList)275     protected void logApCallbackMockable(String ifaceName,
276             List<SupplicantStaIfaceHal.QosPolicyStatus> halStatusList) {
277         Log.i(TAG, "Received AP callback on " + ifaceName + ", size=" + halStatusList.size());
278         if (mVerboseLoggingEnabled) {
279             long numPoliciesAccepted = halStatusList.stream()
280                     .filter(status -> status.statusCode
281                             == SupplicantStaIfaceHal.QOS_POLICY_SCS_RESPONSE_STATUS_SUCCESS)
282                     .count();
283             Log.d(TAG, "AP accepted " + numPoliciesAccepted + " policies");
284         }
285     }
286 
287     /**
288      * Check whether the Application QoS policy feature is enabled.
289      *
290      * @return true if the feature is enabled, false otherwise.
291      */
isFeatureEnabled()292     public boolean isFeatureEnabled() {
293         // Both the experiment flag and overlay value must be enabled,
294         // and the HAL must support this feature.
295         return mDeviceConfigFacade.isApplicationQosPolicyApiEnabled()
296                 && mContext.getResources().getBoolean(
297                 R.bool.config_wifiApplicationCentricQosPolicyFeatureEnabled)
298                 && mWifiNative.isSupplicantAidlServiceVersionAtLeast(2);
299     }
300 
301     /**
302      * Request to add a list of new QoS policies.
303      *
304      * @param policies List of {@link QosPolicyParams} objects representing the policies.
305      * @param listener Listener to call when the operation is complete.
306      * @param uid UID of the requesting application.
307      */
queueAddRequest(@onNull List<QosPolicyParams> policies, @NonNull IListListener listener, @NonNull IBinder binder, int uid)308     public void queueAddRequest(@NonNull List<QosPolicyParams> policies,
309             @NonNull IListListener listener, @NonNull IBinder binder, int uid) {
310         Log.i(TAG, "Queueing add request. size=" + policies.size());
311         QueuedRequest request = new QueuedRequest(
312                 REQUEST_TYPE_ADD, policies, null, listener, binder, uid);
313         queueRequestOnAllIfaces(request);
314         processNextRequestOnAllIfacesIfPossible();
315     }
316 
317     /**
318      * Request to remove a list of existing QoS policies.
319      *
320      * @param policyIds List of integer policy IDs.
321      * @param uid UID of the requesting application.
322      */
queueRemoveRequest(@onNull List<Integer> policyIds, int uid)323     public void queueRemoveRequest(@NonNull List<Integer> policyIds, int uid) {
324         Log.i(TAG, "Queueing remove request. size=" + policyIds.size());
325         QueuedRequest request = new QueuedRequest(
326                 REQUEST_TYPE_REMOVE, null, policyIds, null, null, uid);
327         queueRequestOnAllIfaces(request);
328         processNextRequestOnAllIfacesIfPossible();
329     }
330 
331     /**
332      * Request to remove all policies owned by this requester.
333      *
334      * @param uid UID of the requesting application.
335      */
queueRemoveAllRequest(int uid)336     public void queueRemoveAllRequest(int uid) {
337         List<Integer> ownedPolicies = mPolicyTrackingTable.getAllPolicyIdsOwnedByUid(uid);
338         Log.i(TAG, "Queueing removeAll request. numOwnedPolicies=" + ownedPolicies.size());
339         if (ownedPolicies.isEmpty()) return;
340 
341         // Divide ownedPolicies into batches of size MAX_POLICIES_PER_TRANSACTION,
342         // and queue each batch on all interfaces.
343         List<List<Integer>> batches = divideRequestIntoBatches(ownedPolicies);
344         for (List<Integer> batch : batches) {
345             QueuedRequest request = new QueuedRequest(
346                     REQUEST_TYPE_REMOVE, null, batch, null, null, uid);
347             queueRequestOnAllIfaces(request);
348         }
349         processNextRequestOnAllIfacesIfPossible();
350     }
351 
352     /**
353      * Request to send all tracked policies to the specified interface.
354      *
355      * @param ifaceName Interface name to send the policies to.
356      */
queueAllPoliciesOnIface(String ifaceName)357     public void queueAllPoliciesOnIface(String ifaceName) {
358         List<QosPolicyParams> policyList = mPolicyTrackingTable.getAllPolicies();
359         Log.i(TAG, "Queueing all policies on iface=" + ifaceName + ". numPolicies="
360                 + policyList.size());
361         if (policyList.isEmpty()) return;
362 
363         // Divide policyList into batches of size MAX_POLICIES_PER_TRANSACTION,
364         // and queue each batch on the specified interface.
365         List<List<QosPolicyParams>> batches = divideRequestIntoBatches(policyList);
366         for (List<QosPolicyParams> batch : batches) {
367             QueuedRequest request = new QueuedRequest(
368                     REQUEST_TYPE_ADD, batch, null, null, null, DEFAULT_UID);
369 
370             // Indicate that all policies have already been processed and are in the table.
371             request.processedOnAnyIface = true;
372             request.initialStatusList = generateStatusList(
373                     batch.size(), WifiManager.QOS_REQUEST_STATUS_TRACKING);
374             queueRequestOnIface(ifaceName, request);
375         }
376         processNextRequestIfPossible(ifaceName);
377     }
378 
queueRequestOnAllIfaces(QueuedRequest request)379     private void queueRequestOnAllIfaces(QueuedRequest request) {
380         List<ClientModeManager> clientModeManagers =
381                 mActiveModeWarden.getInternetConnectivityClientModeManagers();
382         if (clientModeManagers.size() == 0) {
383             // Reject request if no ClientModeManagers are available.
384             request.callback.sendResult(request.policiesToAdd.size(),
385                     WifiManager.QOS_REQUEST_STATUS_INSUFFICIENT_RESOURCES);
386             return;
387         }
388 
389         // Pre-process each request before queueing.
390         if (request.requestType == REQUEST_TYPE_ADD) {
391             List<Integer> statusList = mPolicyTrackingTable.addPolicies(
392                     request.policiesToAdd, request.requesterUid);
393             List<QosPolicyParams> acceptedPolicies =
394                     filterPoliciesByStatusList(request.policiesToAdd, statusList);
395             if (acceptedPolicies.isEmpty()) {
396                 // Tracking table rejected all policies in the request. Table may be full,
397                 // or all policies are already being tracked.
398                 request.callback.sendResult(statusList);
399                 return;
400             }
401             request.initialStatusList = statusList;
402         } else if (request.requestType == REQUEST_TYPE_REMOVE) {
403             List<Integer> virtualPolicyIds = mPolicyTrackingTable.translatePolicyIds(
404                     request.policyIdsToRemove, request.requesterUid);
405             if (virtualPolicyIds.isEmpty()) {
406                 // None of these policies are being tracked by the table.
407                 return;
408             }
409             mPolicyTrackingTable.removePolicies(request.policyIdsToRemove, request.requesterUid);
410 
411             List<Byte> virtualPolicyIdBytes = new ArrayList<>();
412             for (int policyId : virtualPolicyIds) {
413                 virtualPolicyIdBytes.add((byte) policyId);
414             }
415             request.virtualPolicyIdsToRemove = virtualPolicyIdBytes;
416 
417             // Unregister death handler if this application no longer owns any policies.
418             unregisterDeathHandlerIfNeeded(request.requesterUid);
419         }
420 
421         for (ClientModeManager cmm : clientModeManagers) {
422             queueRequestOnIface(cmm.getInterfaceName(), request);
423         }
424     }
425 
queueRequestOnIface(String ifaceName, QueuedRequest request)426     private void queueRequestOnIface(String ifaceName, QueuedRequest request) {
427         if (!mPerIfaceRequestQueue.containsKey(ifaceName)) {
428             mPerIfaceRequestQueue.put(ifaceName, new ArrayList<>());
429         }
430         mPerIfaceRequestQueue.get(ifaceName).add(request);
431     }
432 
processNextRequestOnAllIfacesIfPossible()433     private void processNextRequestOnAllIfacesIfPossible() {
434         for (String ifaceName : mPerIfaceRequestQueue.keySet()) {
435             processNextRequestIfPossible(ifaceName);
436         }
437     }
438 
processNextRequestIfPossible(String ifaceName)439     private void processNextRequestIfPossible(String ifaceName) {
440         if (mPendingCallbacks.containsKey(ifaceName)) {
441             // Supplicant is still processing a request on this interface.
442             return;
443         } else if (mPerIfaceRequestQueue.get(ifaceName).isEmpty()) {
444             // No requests in this queue.
445             return;
446         }
447 
448         QueuedRequest request = mPerIfaceRequestQueue.get(ifaceName).get(0);
449         mPerIfaceRequestQueue.get(ifaceName).remove(0);
450         if (request.requestType == REQUEST_TYPE_ADD) {
451             processAddRequest(ifaceName, request);
452         } else if (request.requestType == REQUEST_TYPE_REMOVE) {
453             processRemoveRequest(ifaceName, request);
454         }
455     }
456 
checkForStalledCallback(String ifaceName, CallbackParams processedParams)457     private void checkForStalledCallback(String ifaceName, CallbackParams processedParams) {
458         CallbackParams pendingParams = mPendingCallbacks.get(ifaceName);
459         if (pendingParams == processedParams) {
460             Log.e(TAG, "Callback timed out. Expected params " + pendingParams);
461             mPendingCallbacks.remove(ifaceName);
462             processNextRequestIfPossible(ifaceName);
463         }
464     }
465 
466     /**
467      * Divide a large request into batches of max size {@link #MAX_POLICIES_PER_TRANSACTION}.
468      */
469     @VisibleForTesting
divideRequestIntoBatches(List<T> request)470     protected <T> List<List<T>> divideRequestIntoBatches(List<T> request) {
471         List<List<T>> batches = new ArrayList<>();
472         int startIndex = 0;
473         int endIndex = Math.min(request.size(), MAX_POLICIES_PER_TRANSACTION);
474         while (startIndex < endIndex) {
475             batches.add(request.subList(startIndex, endIndex));
476             startIndex += MAX_POLICIES_PER_TRANSACTION;
477             endIndex = Math.min(request.size(), endIndex + MAX_POLICIES_PER_TRANSACTION);
478         }
479         return batches;
480     }
481 
generateStatusList(int size, @WifiManager.QosRequestStatus int status)482     private List<Integer> generateStatusList(int size, @WifiManager.QosRequestStatus int status) {
483         List<Integer> statusList = new ArrayList<>();
484         for (int i = 0; i < size; i++) {
485             statusList.add(status);
486         }
487         return statusList;
488     }
489 
490     /**
491      * Filter out policies that do not have status code
492      * {@link WifiManager#QOS_REQUEST_STATUS_TRACKING}.
493      */
filterPoliciesByStatusList(List<QosPolicyParams> policyList, List<Integer> statusList)494     private List<QosPolicyParams> filterPoliciesByStatusList(List<QosPolicyParams> policyList,
495             List<Integer> statusList) {
496         List<QosPolicyParams> filteredPolicies = new ArrayList<>();
497         for (int i = 0; i < statusList.size(); i++) {
498             if (statusList.get(i) == WifiManager.QOS_REQUEST_STATUS_TRACKING) {
499                 filteredPolicies.add(policyList.get(i));
500             }
501         }
502         return filteredPolicies;
503     }
504 
processAddRequest(String ifaceName, QueuedRequest request)505     private void processAddRequest(String ifaceName, QueuedRequest request) {
506         boolean previouslyProcessed = request.processedOnAnyIface;
507         request.processedOnAnyIface = true;
508         if (mVerboseLoggingEnabled) {
509             Log.d(TAG, "Processing add request on iface=" + ifaceName + ", size="
510                     + request.policiesToAdd.size());
511         }
512 
513         // Verify that the requesting application is still alive.
514         if (request.binder != null && !request.binder.pingBinder()) {
515             Log.e(TAG, "Requesting application died before processing. request=" + request);
516             processNextRequestIfPossible(ifaceName);
517             return;
518         }
519 
520         // Filter out policies that were already in the table during pre-processing.
521         List<Integer> statusList = new ArrayList(request.initialStatusList);
522         List<QosPolicyParams> policyList = filterPoliciesByStatusList(
523                 request.policiesToAdd, request.initialStatusList);
524 
525         // Filter out policies that were removed from the table in processSynchronousHalResponse().
526         // Only applies to new policy requests that are queued on multiple interfaces.
527         if (previouslyProcessed && request.requesterUid != DEFAULT_UID) {
528             policyList = mPolicyTrackingTable.filterUntrackedPolicies(policyList,
529                     request.requesterUid);
530         }
531 
532         if (policyList.isEmpty()) {
533             Log.e(TAG, "All policies were removed during filtering");
534             processNextRequestIfPossible(ifaceName);
535             return;
536         }
537 
538         List<SupplicantStaIfaceHal.QosPolicyStatus> halStatusList =
539                 mWifiNative.addQosPolicyRequestForScs(ifaceName, policyList);
540         if (halStatusList == null) {
541             if (!previouslyProcessed) {
542                 statusList = handleHalPolicyAddError(
543                         statusList, request.policiesToAdd, request.requesterUid);
544                 request.callback.sendResult(statusList);
545             }
546             processNextRequestIfPossible(ifaceName);
547             return;
548         }
549 
550         if (!previouslyProcessed) {
551             // Send the status list to the requesting application.
552             // Should only be done the first time that a request is processed.
553             statusList = processSynchronousHalResponse(
554                     statusList, halStatusList, request.policiesToAdd, request.requesterUid);
555             request.callback.sendResult(statusList);
556 
557             // Register death handler if this application owns any policies in the table.
558             registerDeathHandlerIfNeeded(request.requesterUid, request.binder);
559         }
560 
561         // Policies that were sent to the AP expect a response from the callback.
562         List<Byte> policiesAwaitingCallback = getPoliciesAwaitingCallback(halStatusList);
563         if (policiesAwaitingCallback.isEmpty()) {
564             processNextRequestIfPossible(ifaceName);
565         } else {
566             CallbackParams cbParams = new CallbackParams(policiesAwaitingCallback);
567             mPendingCallbacks.put(ifaceName, cbParams);
568             mHandler.postDelayed(() -> checkForStalledCallback(ifaceName, cbParams),
569                     CALLBACK_TIMEOUT_MILLIS);
570         }
571     }
572 
processRemoveRequest(String ifaceName, QueuedRequest request)573     private void processRemoveRequest(String ifaceName, QueuedRequest request) {
574         if (mVerboseLoggingEnabled) {
575             Log.i(TAG, "Processing remove request on iface=" + ifaceName + ", size="
576                     + request.policyIdsToRemove.size());
577         }
578         List<SupplicantStaIfaceHal.QosPolicyStatus> halStatusList =
579                 mWifiNative.removeQosPolicyForScs(ifaceName, request.virtualPolicyIdsToRemove);
580         if (halStatusList == null) {
581             processNextRequestIfPossible(ifaceName);
582             return;
583         }
584 
585         // Policies that were sent to the AP expect a response from the callback.
586         List<Byte> policiesAwaitingCallback = getPoliciesAwaitingCallback(halStatusList);
587         if (policiesAwaitingCallback.isEmpty()) {
588             processNextRequestIfPossible(ifaceName);
589         } else {
590             CallbackParams cbParams = new CallbackParams(policiesAwaitingCallback);
591             mPendingCallbacks.put(ifaceName, cbParams);
592             mHandler.postDelayed(() -> checkForStalledCallback(ifaceName, cbParams),
593                     CALLBACK_TIMEOUT_MILLIS);
594         }
595     }
596 
597     /**
598      * Get the list of policy IDs that are expected in the AP callback.
599      *
600      * Any policies that were sent to the AP will appear in the list.
601      */
getPoliciesAwaitingCallback( List<SupplicantStaIfaceHal.QosPolicyStatus> halStatusList)602     private List<Byte> getPoliciesAwaitingCallback(
603             List<SupplicantStaIfaceHal.QosPolicyStatus> halStatusList) {
604         List<Byte> policiesAwaitingCallback = new ArrayList<>();
605         for (SupplicantStaIfaceHal.QosPolicyStatus status : halStatusList) {
606             if (status.statusCode == SupplicantStaIfaceHal.QOS_POLICY_SCS_REQUEST_STATUS_SENT) {
607                 policiesAwaitingCallback.add((byte) status.policyId);
608             }
609         }
610 
611         if (mVerboseLoggingEnabled) {
612             Log.d(TAG, policiesAwaitingCallback.size()
613                     + " policies were sent to the AP and are awaiting callback");
614         }
615         return policiesAwaitingCallback;
616     }
617 
halToWifiManagerSyncStatus( @upplicantStaIfaceHal.QosPolicyScsRequestStatusCode int halStatus)618     private static @WifiManager.QosRequestStatus int halToWifiManagerSyncStatus(
619             @SupplicantStaIfaceHal.QosPolicyScsRequestStatusCode int halStatus) {
620         switch (halStatus) {
621             case SupplicantStaIfaceHal.QOS_POLICY_SCS_REQUEST_STATUS_SENT:
622                 return WifiManager.QOS_REQUEST_STATUS_TRACKING;
623             case SupplicantStaIfaceHal.QOS_POLICY_SCS_REQUEST_STATUS_ALREADY_ACTIVE:
624                 return WifiManager.QOS_REQUEST_STATUS_ALREADY_ACTIVE;
625             case SupplicantStaIfaceHal.QOS_POLICY_SCS_REQUEST_STATUS_INVALID:
626                 return WifiManager.QOS_REQUEST_STATUS_INVALID_PARAMETERS;
627             default:
628                 return WifiManager.QOS_REQUEST_STATUS_FAILURE_UNKNOWN;
629         }
630     }
631 
632     /**
633      * Handle the case where {@link WifiNative#addQosPolicyRequestForScs(String, List)} fails.
634      *
635      * For any policy that was sent to the HAL, assign the proper error code and
636      * remove that policy from the tracking table.
637      */
handleHalPolicyAddError(List<Integer> statusList, List<QosPolicyParams> policyList, int uid)638     private List<Integer> handleHalPolicyAddError(List<Integer> statusList,
639             List<QosPolicyParams> policyList, int uid) {
640         List<Integer> rejectedPolicies = new ArrayList<>();
641         for (int i = 0; i < statusList.size(); i++) {
642             if (statusList.get(i) != WifiManager.QOS_REQUEST_STATUS_TRACKING) {
643                 // Policy was assigned an error code by the tracking table
644                 // and was not sent to the HAL.
645                 continue;
646             }
647             statusList.set(i, WifiManager.QOS_REQUEST_STATUS_FAILURE_UNKNOWN);
648             rejectedPolicies.add(policyList.get(i).getPolicyId());
649         }
650 
651         // Remove policies that were sent to the HAL from the tracking table.
652         mPolicyTrackingTable.removePolicies(rejectedPolicies, uid);
653         return statusList;
654     }
655 
656     /**
657      * Process the status list from {@link WifiNative#addQosPolicyRequestForScs(String, List)}.
658      *
659      * For each policy that was sent to the HAL, merge the HAL status into the main status list.
660      * If any policies were rejected by the HAL, remove them from the policy tracking table.
661      */
662     @VisibleForTesting
processSynchronousHalResponse(List<Integer> statusList, List<SupplicantStaIfaceHal.QosPolicyStatus> halResults, List<QosPolicyParams> policyList, int uid)663     protected List<Integer> processSynchronousHalResponse(List<Integer> statusList,
664             List<SupplicantStaIfaceHal.QosPolicyStatus> halResults,
665             List<QosPolicyParams> policyList, int uid) {
666         int halIndex = 0;
667         List<Integer> rejectedPolicies = new ArrayList<>();
668         for (int i = 0; i < statusList.size(); i++) {
669             if (statusList.get(i) != WifiManager.QOS_REQUEST_STATUS_TRACKING) {
670                 // Policy was assigned an error code by the tracking table
671                 // and was not sent to the HAL.
672                 continue;
673             }
674             int statusCode = halToWifiManagerSyncStatus(halResults.get(halIndex).statusCode);
675             if (statusCode != WifiManager.QOS_REQUEST_STATUS_TRACKING) {
676                 rejectedPolicies.add(policyList.get(i).getPolicyId());
677             }
678             statusList.set(i, statusCode);
679             halIndex++;
680         }
681 
682         if (!rejectedPolicies.isEmpty()) {
683             // Remove policies rejected by the HAL from the tracking table.
684             mPolicyTrackingTable.removePolicies(rejectedPolicies, uid);
685         }
686         return statusList;
687     }
688 
689     /**
690      * Register death handler for this application if it owns policies in the tracking table,
691      * and no death handlers have been registered before.
692      */
registerDeathHandlerIfNeeded(int uid, @NonNull IBinder binder)693     private void registerDeathHandlerIfNeeded(int uid, @NonNull IBinder binder) {
694         if (mApplicationUidToBinderMap.containsKey(uid)) {
695             // Application has already been linked to the death recipient.
696             return;
697         } else if (!mPolicyTrackingTable.tableContainsUid(uid)) {
698             // Application does not own any policies in the tracking table.
699             return;
700         }
701 
702         try {
703             binder.linkToDeath(mApplicationDeathRecipient, /* flags */ 0);
704             mApplicationBinderToUidMap.put(binder, uid);
705             mApplicationUidToBinderMap.put(uid, binder);
706         } catch (RemoteException e) {
707             Log.wtf(TAG, "Exception occurred while linking to death: " + e);
708         }
709     }
710 
711     /**
712      * Unregister the death handler for this application if it
713      * no longer owns any policies in the tracking table.
714      */
unregisterDeathHandlerIfNeeded(int uid)715     private void unregisterDeathHandlerIfNeeded(int uid) {
716         if (!mApplicationUidToBinderMap.containsKey(uid)) {
717             // Application has already been unlinked from the death recipient.
718             return;
719         } else if (mPolicyTrackingTable.tableContainsUid(uid)) {
720             // Application still owns policies in the tracking table.
721             return;
722         }
723 
724         IBinder binder = mApplicationUidToBinderMap.get(uid);
725         binder.unlinkToDeath(mApplicationDeathRecipient, /* flags */ 0);
726         mApplicationBinderToUidMap.remove(binder);
727         mApplicationUidToBinderMap.remove(uid);
728     }
729 
730     /**
731      * Dump information about the internal state.
732      *
733      * @param pw PrintWriter to write the dump to.
734      */
dump(PrintWriter pw)735     public void dump(PrintWriter pw) {
736         pw.println("Dump of ApplicationQosPolicyRequestHandler");
737         pw.println("mPerIfaceRequestQueue: " + mPerIfaceRequestQueue);
738         pw.println("mPendingCallbacks: " + mPendingCallbacks);
739         pw.println();
740         mPolicyTrackingTable.dump(pw);
741     }
742 }
743