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