• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.internal.telephony.data;
18 
19 import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
20 import static android.telephony.CarrierConfigManager.KEY_DATA_SWITCH_VALIDATION_MIN_GAP_LONG;
21 import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
22 
23 import android.content.Context;
24 import android.net.ConnectivityManager;
25 import android.net.Network;
26 import android.net.NetworkCapabilities;
27 import android.net.NetworkRequest;
28 import android.net.TelephonyNetworkSpecifier;
29 import android.os.Handler;
30 import android.os.PersistableBundle;
31 import android.telephony.CarrierConfigManager;
32 import android.telephony.CellIdentity;
33 import android.telephony.CellIdentityLte;
34 import android.telephony.CellInfo;
35 import android.telephony.NetworkRegistrationInfo;
36 import android.telephony.SubscriptionManager;
37 import android.util.Log;
38 
39 import com.android.internal.annotations.VisibleForTesting;
40 import com.android.internal.telephony.Phone;
41 import com.android.internal.telephony.PhoneConfigurationManager;
42 import com.android.internal.telephony.PhoneFactory;
43 import com.android.internal.telephony.SubscriptionController;
44 import com.android.internal.telephony.metrics.TelephonyMetrics;
45 import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent;
46 
47 import java.util.Comparator;
48 import java.util.HashMap;
49 import java.util.Map;
50 import java.util.PriorityQueue;
51 import java.util.concurrent.TimeUnit;
52 
53 /**
54  * This class will validate whether cellular network verified by Connectivity's
55  * validation process. It listens request on a specific subId, sends a network request
56  * to Connectivity and listens to its callback or timeout.
57  */
58 public class CellularNetworkValidator {
59     private static final String LOG_TAG = "NetworkValidator";
60     // If true, upon validated network cache hit, we report validationDone only when
61     // network becomes available. Otherwise, we report validationDone immediately.
62     private static boolean sWaitForNetworkAvailableWhenCacheHit = true;
63 
64     // States of validator. Only one validation can happen at once.
65     // IDLE: no validation going on.
66     private static final int STATE_IDLE                = 0;
67     // VALIDATING: validation going on.
68     private static final int STATE_VALIDATING          = 1;
69     // VALIDATED: validation is done and successful.
70     // Waiting for stopValidation() to release
71     // validationg NetworkRequest.
72     private static final int STATE_VALIDATED           = 2;
73 
74     // Singleton instance.
75     private static CellularNetworkValidator sInstance;
76     @VisibleForTesting
77     public static final long MAX_VALIDATION_CACHE_TTL = TimeUnit.DAYS.toMillis(1);
78 
79     private int mState = STATE_IDLE;
80     private int mSubId;
81     private long mTimeoutInMs;
82     private boolean mReleaseAfterValidation;
83 
84     private NetworkRequest mNetworkRequest;
85     private ValidationCallback mValidationCallback;
86     private Context mContext;
87     private ConnectivityManager mConnectivityManager;
88     @VisibleForTesting
89     public Handler mHandler = new Handler();
90     @VisibleForTesting
91     public ConnectivityNetworkCallback mNetworkCallback;
92     private final ValidatedNetworkCache mValidatedNetworkCache = new ValidatedNetworkCache();
93 
94     private class ValidatedNetworkCache {
95         // A cache with fixed size. It remembers 10 most recently successfully validated networks.
96         private static final int VALIDATED_NETWORK_CACHE_SIZE = 10;
97         private final PriorityQueue<ValidatedNetwork> mValidatedNetworkPQ =
98                 new PriorityQueue((Comparator<ValidatedNetwork>) (n1, n2) -> {
99                     if (n1.mValidationTimeStamp < n2.mValidationTimeStamp) {
100                         return -1;
101                     } else if (n1.mValidationTimeStamp > n2.mValidationTimeStamp) {
102                         return 1;
103                     } else {
104                         return 0;
105                     }
106                 });
107         private final Map<String, ValidatedNetwork> mValidatedNetworkMap = new HashMap();
108 
109         private final class ValidatedNetwork {
ValidatedNetwork(String identity, long timeStamp)110             ValidatedNetwork(String identity, long timeStamp) {
111                 mValidationIdentity = identity;
112                 mValidationTimeStamp = timeStamp;
113             }
update(long timeStamp)114             void update(long timeStamp) {
115                 mValidationTimeStamp = timeStamp;
116             }
117             final String mValidationIdentity;
118             long mValidationTimeStamp;
119         }
120 
isRecentlyValidated(int subId)121         synchronized boolean isRecentlyValidated(int subId) {
122             long cacheTtl = getValidationCacheTtl(subId);
123             String networkIdentity = getValidationNetworkIdentity(subId);
124             if (networkIdentity == null || !mValidatedNetworkMap.containsKey(networkIdentity)) {
125                 return false;
126             }
127             long validatedTime = mValidatedNetworkMap.get(networkIdentity).mValidationTimeStamp;
128             boolean recentlyValidated = System.currentTimeMillis() - validatedTime < cacheTtl;
129             logd("isRecentlyValidated on subId " + subId + " ? " + recentlyValidated);
130             return recentlyValidated;
131         }
132 
133         synchronized void storeLastValidationResult(int subId, boolean validated) {
134             String networkIdentity = getValidationNetworkIdentity(subId);
135             logd("storeLastValidationResult for subId " + subId
136                     + (validated ? " validated." : " not validated."));
137             if (networkIdentity == null) return;
138 
139             if (!validated) {
140                 // If validation failed, clear it from the cache.
141                 mValidatedNetworkPQ.remove(mValidatedNetworkMap.get(networkIdentity));
142                 mValidatedNetworkMap.remove(networkIdentity);
143                 return;
144             }
145             long time =  System.currentTimeMillis();
146             ValidatedNetwork network = mValidatedNetworkMap.get(networkIdentity);
147             if (network != null) {
148                 // Already existed in cache, update.
149                 network.update(time);
150                 // Re-add to re-sort.
151                 mValidatedNetworkPQ.remove(network);
152                 mValidatedNetworkPQ.add(network);
153             } else {
154                 network = new ValidatedNetwork(networkIdentity, time);
155                 mValidatedNetworkMap.put(networkIdentity, network);
156                 mValidatedNetworkPQ.add(network);
157             }
158             // If exceeded max size, remove the one with smallest validation timestamp.
159             if (mValidatedNetworkPQ.size() > VALIDATED_NETWORK_CACHE_SIZE) {
160                 ValidatedNetwork networkToRemove = mValidatedNetworkPQ.poll();
161                 mValidatedNetworkMap.remove(networkToRemove.mValidationIdentity);
162             }
163         }
164 
165         private String getValidationNetworkIdentity(int subId) {
166             if (!SubscriptionManager.isUsableSubscriptionId(subId)) return null;
167             SubscriptionController subController = SubscriptionController.getInstance();
168             if (subController == null) return null;
169             Phone phone = PhoneFactory.getPhone(subController.getPhoneId(subId));
170             if (phone == null || phone.getServiceState() == null) return null;
171 
172             NetworkRegistrationInfo regInfo = phone.getServiceState().getNetworkRegistrationInfo(
173                     DOMAIN_PS, TRANSPORT_TYPE_WWAN);
174             if (regInfo == null || regInfo.getCellIdentity() == null) return null;
175 
176             CellIdentity cellIdentity = regInfo.getCellIdentity();
177             // TODO: add support for other technologies.
178             if (cellIdentity.getType() != CellInfo.TYPE_LTE
179                     || cellIdentity.getMccString() == null || cellIdentity.getMncString() == null
180                     || ((CellIdentityLte) cellIdentity).getTac() == CellInfo.UNAVAILABLE) {
181                 return null;
182             }
183 
184             return cellIdentity.getMccString() + cellIdentity.getMncString() + "_"
185                     + ((CellIdentityLte) cellIdentity).getTac() + "_" + subId;
186         }
187 
188         private long getValidationCacheTtl(int subId) {
189             long ttl = 0;
190             CarrierConfigManager configManager = (CarrierConfigManager)
191                     mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
192             if (configManager != null) {
193                 PersistableBundle b = configManager.getConfigForSubId(subId);
194                 if (b != null) {
195                     ttl = b.getLong(KEY_DATA_SWITCH_VALIDATION_MIN_GAP_LONG);
196                 }
197             }
198             // Ttl can't be bigger than one day for now.
199             return Math.min(ttl, MAX_VALIDATION_CACHE_TTL);
200         }
201     }
202 
203     /**
204      * Callback to pass in when starting validation.
205      */
206     public interface ValidationCallback {
207         /**
208          * Validation failed, passed or timed out.
209          */
210         void onValidationDone(boolean validated, int subId);
211         /**
212          * Called when a corresponding network becomes available.
213          */
214         void onNetworkAvailable(Network network, int subId);
215     }
216 
217     /**
218      * Create instance.
219      */
220     public static CellularNetworkValidator make(Context context) {
221         if (sInstance != null) {
222             logd("createCellularNetworkValidator failed. Instance already exists.");
223         } else {
224             sInstance = new CellularNetworkValidator(context);
225         }
226 
227         return sInstance;
228     }
229 
230     /**
231      * Get instance.
232      */
233     public static CellularNetworkValidator getInstance() {
234         return sInstance;
235     }
236 
237     /**
238      * Check whether this feature is supported or not.
239      */
240     public boolean isValidationFeatureSupported() {
241         return PhoneConfigurationManager.getInstance().getCurrentPhoneCapability()
242                 .isNetworkValidationBeforeSwitchSupported();
243     }
244 
245     @VisibleForTesting
246     public CellularNetworkValidator(Context context) {
247         mContext = context;
248         mConnectivityManager = (ConnectivityManager)
249                 mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
250     }
251 
252     /**
253      * API to start a validation
254      */
255     public synchronized void validate(int subId, long timeoutInMs,
256             boolean releaseAfterValidation, ValidationCallback callback) {
257         // If it's already validating the same subscription, do nothing.
258         if (subId == mSubId) return;
259 
260         if (!SubscriptionController.getInstance().isActiveSubId(subId)) {
261             logd("Failed to start validation. Inactive subId " + subId);
262             callback.onValidationDone(false, subId);
263             return;
264         }
265 
266         if (isValidating()) {
267             stopValidation();
268         }
269 
270         if (!sWaitForNetworkAvailableWhenCacheHit && mValidatedNetworkCache
271                 .isRecentlyValidated(subId)) {
272             callback.onValidationDone(true, subId);
273             return;
274         }
275 
276         mState = STATE_VALIDATING;
277         mSubId = subId;
278         mTimeoutInMs = timeoutInMs;
279         mValidationCallback = callback;
280         mReleaseAfterValidation = releaseAfterValidation;
281         mNetworkRequest = createNetworkRequest();
282 
283         logd("Start validating subId " + mSubId + " mTimeoutInMs " + mTimeoutInMs
284                 + " mReleaseAfterValidation " + mReleaseAfterValidation);
285 
286         mNetworkCallback = new ConnectivityNetworkCallback(subId);
287 
288         mConnectivityManager.requestNetwork(mNetworkRequest, mNetworkCallback, mHandler);
289         mHandler.postDelayed(() -> onValidationTimeout(subId), mTimeoutInMs);
290     }
291 
292     private synchronized void onValidationTimeout(int subId) {
293         logd("timeout on subId " + subId + " validation.");
294         // Remember latest validated network.
295         mValidatedNetworkCache.storeLastValidationResult(subId, false);
296         reportValidationResult(false, subId);
297     }
298 
299     /**
300      * API to stop the current validation.
301      */
302     public synchronized void stopValidation() {
303         if (!isValidating()) {
304             logd("No need to stop validation.");
305             return;
306         }
307         if (mNetworkCallback != null) {
308             mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
309         }
310         mState = STATE_IDLE;
311         mHandler.removeCallbacksAndMessages(null);
312         mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
313     }
314 
315     /**
316      * Return which subscription is under validating.
317      */
318     public synchronized int getSubIdInValidation() {
319         return mSubId;
320     }
321 
322     /**
323      * Return whether there's an ongoing validation.
324      */
325     public synchronized boolean isValidating() {
326         return mState != STATE_IDLE;
327     }
328 
329     private NetworkRequest createNetworkRequest() {
330         return new NetworkRequest.Builder()
331                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
332                 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
333                 .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
334                         .setSubscriptionId(mSubId).build())
335                 .build();
336     }
337 
338     private synchronized void reportValidationResult(boolean passed, int subId) {
339         // If the validation result is not for current subId, do nothing.
340         if (mSubId != subId) return;
341 
342         mHandler.removeCallbacksAndMessages(null);
343 
344         // Deal with the result only when state is still VALIDATING. This is to avoid
345         // receiving multiple callbacks in queue.
346         if (mState == STATE_VALIDATING) {
347             mValidationCallback.onValidationDone(passed, mSubId);
348             mState = STATE_VALIDATED;
349             // If validation passed and per request to NOT release after validation, delay cleanup.
350             if (!mReleaseAfterValidation && passed) {
351                 mHandler.postDelayed(()-> stopValidation(), 500);
352             } else {
353                 stopValidation();
354             }
355 
356             TelephonyMetrics.getInstance().writeNetworkValidate(passed
357                     ? TelephonyEvent.NetworkValidationState.NETWORK_VALIDATION_STATE_PASSED
358                     : TelephonyEvent.NetworkValidationState.NETWORK_VALIDATION_STATE_FAILED);
359         }
360     }
361 
362     private synchronized void reportNetworkAvailable(Network network, int subId) {
363         // If the validation result is not for current subId, do nothing.
364         if (mSubId != subId) return;
365         mValidationCallback.onNetworkAvailable(network, subId);
366     }
367 
368     @VisibleForTesting
369     public class ConnectivityNetworkCallback extends ConnectivityManager.NetworkCallback {
370         private final int mSubId;
371 
372         ConnectivityNetworkCallback(int subId) {
373             mSubId = subId;
374         }
375         /**
376          * ConnectivityManager.NetworkCallback implementation
377          */
378         @Override
379         public void onAvailable(Network network) {
380             logd("network onAvailable " + network);
381             TelephonyMetrics.getInstance().writeNetworkValidate(
382                     TelephonyEvent.NetworkValidationState.NETWORK_VALIDATION_STATE_AVAILABLE);
383             // If it hits validation cache, we report as validation passed; otherwise we report
384             // network is available.
385             if (mValidatedNetworkCache.isRecentlyValidated(mSubId)) {
386                 reportValidationResult(true, ConnectivityNetworkCallback.this.mSubId);
387             } else {
388                 reportNetworkAvailable(network, ConnectivityNetworkCallback.this.mSubId);
389             }
390         }
391 
392         @Override
393         public void onLosing(Network network, int maxMsToLive) {
394             logd("network onLosing " + network + " maxMsToLive " + maxMsToLive);
395             mValidatedNetworkCache.storeLastValidationResult(
396                     ConnectivityNetworkCallback.this.mSubId, false);
397             reportValidationResult(false, ConnectivityNetworkCallback.this.mSubId);
398         }
399 
400         @Override
401         public void onLost(Network network) {
402             logd("network onLost " + network);
403             mValidatedNetworkCache.storeLastValidationResult(
404                     ConnectivityNetworkCallback.this.mSubId, false);
405             reportValidationResult(false, ConnectivityNetworkCallback.this.mSubId);
406         }
407 
408         @Override
409         public void onUnavailable() {
410             logd("onUnavailable");
411             mValidatedNetworkCache.storeLastValidationResult(
412                     ConnectivityNetworkCallback.this.mSubId, false);
413             reportValidationResult(false, ConnectivityNetworkCallback.this.mSubId);
414         }
415 
416         @Override
417         public void onCapabilitiesChanged(Network network,
418                 NetworkCapabilities networkCapabilities) {
419             if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
420                 logd("onValidated");
421                 mValidatedNetworkCache.storeLastValidationResult(
422                         ConnectivityNetworkCallback.this.mSubId, true);
423                 reportValidationResult(true, ConnectivityNetworkCallback.this.mSubId);
424             }
425         }
426     }
427 
428     private static void logd(String log) {
429         Log.d(LOG_TAG, log);
430     }
431 }
432