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