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