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