• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2025 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.networkstack.tethering;
18 
19 import static com.android.networkstack.tethering.util.TetheringUtils.createPlaceholderRequest;
20 
21 import android.net.TetheringManager.TetheringRequest;
22 import android.net.ip.IpServer;
23 import android.util.ArrayMap;
24 import android.util.Log;
25 
26 import androidx.annotation.NonNull;
27 import androidx.annotation.Nullable;
28 
29 import com.android.internal.annotations.VisibleForTesting;
30 
31 import java.util.ArrayList;
32 import java.util.List;
33 import java.util.Map;
34 
35 /**
36  * Helper class to keep track of tethering requests.
37  * The intended usage of this class is
38  * 1) Add a pending request with {@link #addPendingRequest(TetheringRequest)} before asking the link
39  *    layer to start.
40  * 2) When the link layer is up, use {@link #getOrCreatePendingRequest(int)} to get a request to
41  *    start IP serving with.
42  * 3) Remove pending request with {@link #removePendingRequest(TetheringRequest)}.
43  * Note: This class is not thread-safe.
44  */
45 public class RequestTracker {
46     private static final String TAG = RequestTracker.class.getSimpleName();
47 
48     @NonNull
49     private final boolean mUseFuzzyMatching;
50 
RequestTracker(boolean useFuzzyMatching)51     public RequestTracker(boolean useFuzzyMatching) {
52         mUseFuzzyMatching = useFuzzyMatching;
53     }
54 
55     public enum AddResult {
56         /**
57          * Request was successfully added
58          */
59         SUCCESS,
60         /**
61          * Failure indicating that the request could not be added due to a request of the same type
62          * with conflicting parameters already pending. If so, we must stop tethering for the
63          * pending request before trying to add the result again. Only returned on V-.
64          */
65         FAILURE_DUPLICATE_REQUEST_RESTART,
66         /**
67          * Failure indicating that the request could not be added due to a fuzzy-matched request
68          * already pending or serving. Only returned on B+.
69          */
70         FAILURE_DUPLICATE_REQUEST_ERROR,
71     }
72 
73     /**
74      * List of pending requests added by {@link #addPendingRequest(TetheringRequest)}
75      * There can be only one per type, since we remove every request of the
76      * same type when we add a request.
77      */
78     private final List<TetheringRequest> mPendingRequests = new ArrayList<>();
79     /**
80      * List of serving requests added by
81      * {@link #promoteRequestToServing(IpServer, TetheringRequest)}.
82      */
83     private final Map<IpServer, TetheringRequest> mServingRequests = new ArrayMap<>();
84 
85     @VisibleForTesting
getPendingTetheringRequests()86     List<TetheringRequest> getPendingTetheringRequests() {
87         return new ArrayList<>(mPendingRequests);
88     }
89 
90     /**
91      * Adds a pending request or fails with FAILURE_CONFLICTING_REQUEST_FAIL if the request
92      * fuzzy-matches an existing request (either pending or serving).
93      */
addPendingRequestFuzzyMatched(@onNull final TetheringRequest newRequest)94     public AddResult addPendingRequestFuzzyMatched(@NonNull final TetheringRequest newRequest) {
95         List<TetheringRequest> existingRequests = new ArrayList<>();
96         existingRequests.addAll(mServingRequests.values());
97         existingRequests.addAll(mPendingRequests);
98         for (TetheringRequest request : existingRequests) {
99             if (request.fuzzyMatches(newRequest)) {
100                 Log.i(TAG, "Cannot add pending request due to existing fuzzy-matched "
101                         + "request: " + request);
102                 return AddResult.FAILURE_DUPLICATE_REQUEST_ERROR;
103             }
104         }
105 
106         mPendingRequests.add(newRequest);
107         return AddResult.SUCCESS;
108     }
109 
110     /**
111      * Add a pending request and listener. The request should be added before asking the link layer
112      * to start, and should be retrieved with {@link #getNextPendingRequest(int)} once the link
113      * layer comes up. The result of the add operation will be returned as an AddResult code.
114      */
addPendingRequest(@onNull final TetheringRequest newRequest)115     public AddResult addPendingRequest(@NonNull final TetheringRequest newRequest) {
116         if (mUseFuzzyMatching) {
117             return addPendingRequestFuzzyMatched(newRequest);
118         }
119 
120         // Check the existing requests to see if it is OK to add the new request.
121         for (TetheringRequest existingRequest : mPendingRequests) {
122             if (existingRequest.getTetheringType() != newRequest.getTetheringType()) {
123                 continue;
124             }
125 
126             // Can't add request if there's a request of the same type with different
127             // parameters.
128             if (!existingRequest.equalsIgnoreUidPackage(newRequest)) {
129                 return AddResult.FAILURE_DUPLICATE_REQUEST_RESTART;
130             }
131         }
132 
133         // Remove the existing pending request of the same type. We already filter out for
134         // conflicting parameters above, so these would have been equivalent anyway (except for
135         // UID).
136         removeAllPendingRequests(newRequest.getTetheringType());
137         mPendingRequests.add(newRequest);
138         return AddResult.SUCCESS;
139     }
140 
141     /**
142      * Gets the next pending TetheringRequest of a given type, or creates a placeholder request if
143      * there are none.
144      * Note: There are edge cases where the pending request is absent and we must temporarily
145      * synthesize a placeholder request, such as if stopTethering was called before link
146      * layer went up, or if the link layer goes up without us poking it (e.g. adb shell
147      * cmd wifi start-softap). These placeholder requests only specify the tethering type
148      * and the default connectivity scope.
149      */
150     @NonNull
getOrCreatePendingRequest(int type)151     public TetheringRequest getOrCreatePendingRequest(int type) {
152         TetheringRequest pending = getNextPendingRequest(type);
153         if (pending != null) return pending;
154 
155         Log.w(TAG, "No pending TetheringRequest for type " + type + " found, creating a"
156                 + " placeholder request");
157         return createPlaceholderRequest(type);
158     }
159 
160     /**
161      * Same as {@link #getOrCreatePendingRequest(int)} but returns {@code null} if there's no
162      * pending request found.
163      *
164      * @param type Tethering type of the pending request
165      * @return pending request or {@code null} if there are none.
166      */
167     @Nullable
getNextPendingRequest(int type)168     public TetheringRequest getNextPendingRequest(int type) {
169         for (TetheringRequest request : mPendingRequests) {
170             if (request.getTetheringType() == type) return request;
171         }
172         return null;
173     }
174 
175     /**
176      * Removes all pending requests of the given tethering type.
177      *
178      * @param type Tethering type
179      */
removeAllPendingRequests(final int type)180     public void removeAllPendingRequests(final int type) {
181         mPendingRequests.removeIf(r -> r.getTetheringType() == type);
182     }
183 
184     /**
185      * Removes a specific pending request.
186      *
187      * Note: For V-, this will be the same as removeAllPendingRequests to align with historical
188      * behavior.
189      *
190      * @param request Request to be removed
191      */
removePendingRequest(@onNull TetheringRequest request)192     public void removePendingRequest(@NonNull TetheringRequest request) {
193         if (!mUseFuzzyMatching) {
194             // Remove all requests of the same type to match the historical behavior.
195             removeAllPendingRequests(request.getTetheringType());
196             return;
197         }
198 
199         mPendingRequests.removeIf(r -> r.equals(request));
200     }
201 
202     /**
203      * Removes a tethering request from the pending list and promotes it to serving with the
204      * IpServer that is using it.
205      * Note: If mUseFuzzyMatching is false, then the request will be removed from the pending list,
206      * but it will not be added to serving list.
207      */
promoteRequestToServing(@onNull final IpServer ipServer, @NonNull final TetheringRequest tetheringRequest)208     public void promoteRequestToServing(@NonNull final IpServer ipServer,
209             @NonNull final TetheringRequest tetheringRequest) {
210         removePendingRequest(tetheringRequest);
211         if (!mUseFuzzyMatching) return;
212         mServingRequests.put(ipServer, tetheringRequest);
213     }
214 
215 
216     /**
217      * Returns the serving request tied to the given IpServer, or null if there is none.
218      * Note: If mUseFuzzyMatching is false, then this will always return null.
219      */
220     @Nullable
getServingRequest(@onNull final IpServer ipServer)221     public TetheringRequest getServingRequest(@NonNull final IpServer ipServer) {
222         return mServingRequests.get(ipServer);
223     }
224 
225     /**
226      * Removes the serving request tied to the given IpServer.
227      * Note: If mUseFuzzyMatching is false, then this is a no-op since serving requests are unused
228      * for that configuration.
229      */
removeServingRequest(@onNull final IpServer ipServer)230     public void removeServingRequest(@NonNull final IpServer ipServer) {
231         mServingRequests.remove(ipServer);
232     }
233 
234     /**
235      * Removes all serving requests of the given tethering type.
236      *
237      * @param type Tethering type
238      */
removeAllServingRequests(final int type)239     public void removeAllServingRequests(final int type) {
240         mServingRequests.entrySet().removeIf(e -> e.getValue().getTetheringType() == type);
241     }
242 
243     @VisibleForTesting
getServingTetheringRequests()244     List<TetheringRequest> getServingTetheringRequests() {
245         return new ArrayList<>(mServingRequests.values());
246     }
247 
248     /**
249      * Returns an existing (pending or serving) request that fuzzy matches the given request.
250      * Optionally specify matchUid to only return requests with the same uid.
251      */
findFuzzyMatchedRequest( @onNull final TetheringRequest tetheringRequest, boolean matchUid)252     public TetheringRequest findFuzzyMatchedRequest(
253             @NonNull final TetheringRequest tetheringRequest, boolean matchUid) {
254         List<TetheringRequest> allRequests = new ArrayList<>();
255         allRequests.addAll(getPendingTetheringRequests());
256         allRequests.addAll(mServingRequests.values());
257         for (TetheringRequest request : allRequests) {
258             if (!request.fuzzyMatches(tetheringRequest)) continue;
259             if (matchUid && tetheringRequest.getUid() != request.getUid()) continue;
260             return request;
261         }
262         return null;
263     }
264 }
265