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