1 /*
2  * Copyright (C) 2021 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.telephony.qns;
18 
19 import android.annotation.NonNull;
20 import android.content.Context;
21 import android.location.Country;
22 import android.location.CountryDetector;
23 import android.net.ConnectivityManager;
24 import android.net.LinkAddress;
25 import android.net.LinkProperties;
26 import android.net.Network;
27 import android.net.NetworkCapabilities;
28 import android.net.NetworkSpecifier;
29 import android.net.TelephonyNetworkSpecifier;
30 import android.net.TransportInfo;
31 import android.net.vcn.VcnTransportInfo;
32 import android.net.vcn.VcnUtils;
33 import android.os.Handler;
34 import android.os.HandlerThread;
35 import android.os.Looper;
36 import android.os.Message;
37 import android.telephony.TelephonyManager;
38 import android.text.TextUtils;
39 import android.util.Log;
40 import android.util.SparseArray;
41 
42 import com.android.internal.annotations.VisibleForTesting;
43 
44 import java.io.PrintWriter;
45 import java.net.Inet4Address;
46 import java.net.Inet6Address;
47 import java.net.InetAddress;
48 import java.util.ArrayList;
49 import java.util.List;
50 import java.util.Map;
51 import java.util.concurrent.ConcurrentHashMap;
52 
53 /**
54  * IwlanNetworkStatusTracker monitors if there is a network available for IWLAN and informs it to
55  * registrants.
56  */
57 class IwlanNetworkStatusTracker {
58     private static final Boolean DBG = true;
59     private static final String sLogTag = IwlanNetworkStatusTracker.class.getSimpleName();
60     private static final int EVENT_BASE = 1000;
61     private static final int EVENT_IWLAN_SERVICE_STATE_CHANGED = EVENT_BASE;
62     private final Map<Integer, QnsRegistrantList> mIwlanNetworkListenersArray =
63             new ConcurrentHashMap<>();
64     private static final String LAST_KNOWN_COUNTRY_CODE_KEY = "last_known_country_code";
65     private static final int INVALID_SUB_ID = -1;
66     private final SparseArray<QnsCarrierConfigManager> mQnsConfigManagers = new SparseArray<>();
67     private final SparseArray<QnsEventDispatcher> mQnsEventDispatchers = new SparseArray<>();
68     private final SparseArray<QnsImsManager> mQnsImsManagers = new SparseArray<>();
69     private final SparseArray<QnsTelephonyListener> mQnsTelephonyListeners = new SparseArray<>();
70     private final Context mContext;
71     private DefaultNetworkCallback mDefaultNetworkCallback;
72     private final HandlerThread mHandlerThread;
73     private final ConnectivityManager mConnectivityManager;
74     private final TelephonyManager mTelephonyManager;
75     private Handler mNetCbHandler;
76     private String mLastKnownCountryCode;
77     private boolean mWifiAvailable = false;
78     private boolean mWifiToggleOn = false;
79     private Map<Integer, Boolean> mIwlanRegistered = new ConcurrentHashMap<>();
80 
81     // The current active data subscription. May not be the default data subscription.
82     private int mConnectedDataSub = INVALID_SUB_ID;
83     @VisibleForTesting SparseArray<IwlanEventHandler> mHandlerSparseArray = new SparseArray<>();
84     @VisibleForTesting SparseArray<IwlanAvailabilityInfo> mLastIwlanAvailabilityInfo =
85             new SparseArray<>();
86     private CountryDetector mCountryDetector;
87 
88     enum LinkProtocolType {
89         UNKNOWN,
90         IPV4,
91         IPV6,
92         IPV4V6;
93     }
94 
95     private static LinkProtocolType sLinkProtocolType = LinkProtocolType.UNKNOWN;
96 
97     class IwlanEventHandler extends Handler {
98         private final int mSlotIndex;
99 
IwlanEventHandler(int slotId, Looper l)100         IwlanEventHandler(int slotId, Looper l) {
101             super(l);
102             mSlotIndex = slotId;
103             List<Integer> events = new ArrayList<>();
104             events.add(QnsEventDispatcher.QNS_EVENT_CROSS_SIM_CALLING_ENABLED);
105             events.add(QnsEventDispatcher.QNS_EVENT_CROSS_SIM_CALLING_DISABLED);
106             events.add(QnsEventDispatcher.QNS_EVENT_WIFI_DISABLING);
107             events.add(QnsEventDispatcher.QNS_EVENT_WIFI_ENABLED);
108             mQnsEventDispatchers.get(mSlotIndex).registerEvent(events, this);
109             mQnsTelephonyListeners
110                     .get(mSlotIndex)
111                     .registerIwlanServiceStateListener(
112                             this, EVENT_IWLAN_SERVICE_STATE_CHANGED, null);
113         }
114 
115         @Override
handleMessage(Message message)116         public void handleMessage(Message message) {
117             Log.d(sLogTag, "handleMessage msg=" + message.what);
118             switch (message.what) {
119                 case QnsEventDispatcher.QNS_EVENT_CROSS_SIM_CALLING_ENABLED:
120                     onCrossSimEnabledEvent(true, mSlotIndex);
121                     break;
122                 case QnsEventDispatcher.QNS_EVENT_CROSS_SIM_CALLING_DISABLED:
123                     onCrossSimEnabledEvent(false, mSlotIndex);
124                     break;
125                 case QnsEventDispatcher.QNS_EVENT_WIFI_ENABLED:
126                     onWifiEnabled();
127                     break;
128                 case QnsEventDispatcher.QNS_EVENT_WIFI_DISABLING:
129                     onWifiDisabling();
130                     break;
131                 case EVENT_IWLAN_SERVICE_STATE_CHANGED:
132                     QnsAsyncResult ar = (QnsAsyncResult) message.obj;
133                     boolean isRegistered = (boolean) ar.mResult;
134                     onIwlanServiceStateChanged(mSlotIndex, isRegistered);
135                     break;
136                 default:
137                     Log.d(sLogTag, "Unknown message received!");
138                     break;
139             }
140         }
141     }
142 
IwlanNetworkStatusTracker(@onNull Context context)143     IwlanNetworkStatusTracker(@NonNull Context context) {
144         mContext = context;
145         mHandlerThread = new HandlerThread(IwlanNetworkStatusTracker.class.getSimpleName());
146         mHandlerThread.start();
147         Looper looper = mHandlerThread.getLooper();
148         mNetCbHandler = new Handler(looper);
149         mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
150         mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
151         mLastIwlanAvailabilityInfo.clear();
152         registerDefaultNetworkCb();
153         Log.d(sLogTag, "Registered with Connectivity Service");
154         startCountryDetector();
155     }
156 
initBySlotIndex( @onNull QnsCarrierConfigManager configManager, @NonNull QnsEventDispatcher dispatcher, @NonNull QnsImsManager imsManager, @NonNull QnsTelephonyListener telephonyListener, int slotId)157     void initBySlotIndex(
158             @NonNull QnsCarrierConfigManager configManager,
159             @NonNull QnsEventDispatcher dispatcher,
160             @NonNull QnsImsManager imsManager,
161             @NonNull QnsTelephonyListener telephonyListener,
162             int slotId) {
163         mQnsConfigManagers.put(slotId, configManager);
164         mQnsEventDispatchers.put(slotId, dispatcher);
165         mQnsImsManagers.put(slotId, imsManager);
166         mQnsTelephonyListeners.put(slotId, telephonyListener);
167         mHandlerSparseArray.put(slotId, new IwlanEventHandler(slotId, mHandlerThread.getLooper()));
168     }
169 
closeBySlotIndex(int slotId)170     void closeBySlotIndex(int slotId) {
171         IwlanEventHandler handler = mHandlerSparseArray.get(slotId);
172         mQnsEventDispatchers.get(slotId).unregisterEvent(handler);
173         mQnsTelephonyListeners.get(slotId).unregisterIwlanServiceStateChanged(handler);
174         mIwlanNetworkListenersArray.remove(slotId);
175         mQnsConfigManagers.remove(slotId);
176         mQnsEventDispatchers.remove(slotId);
177         mQnsImsManagers.remove(slotId);
178         mQnsTelephonyListeners.remove(slotId);
179         mHandlerSparseArray.remove(slotId);
180     }
181 
182     @VisibleForTesting
onCrossSimEnabledEvent(boolean enabled, int slotId)183     void onCrossSimEnabledEvent(boolean enabled, int slotId) {
184         Log.d(sLogTag, "onCrossSimEnabledEvent enabled:" + enabled + " slotIndex:" + slotId);
185         if (enabled) {
186             int activeDataSub = INVALID_SUB_ID;
187             NetworkSpecifier specifier;
188             final Network activeNetwork = mConnectivityManager.getActiveNetwork();
189             if (activeNetwork != null) {
190                 final NetworkCapabilities nc =
191                         mConnectivityManager.getNetworkCapabilities(activeNetwork);
192                 if (nc != null && nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
193                     specifier = nc.getNetworkSpecifier();
194                     TransportInfo transportInfo = nc.getTransportInfo();
195                     if (transportInfo instanceof VcnTransportInfo) {
196                         activeDataSub = VcnUtils.getSubIdFromVcnCaps(mConnectivityManager, nc);
197                     } else if (specifier instanceof TelephonyNetworkSpecifier) {
198                         activeDataSub = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
199                     }
200                     if (activeDataSub != INVALID_SUB_ID && activeDataSub != mConnectedDataSub) {
201                         mConnectedDataSub = activeDataSub;
202                     }
203                 }
204             }
205             notifyIwlanNetworkStatus();
206         } else {
207             notifyIwlanNetworkStatus(true);
208         }
209     }
210 
211     @VisibleForTesting
onWifiEnabled()212     void onWifiEnabled() {
213         mWifiToggleOn = true;
214         if (!mWifiAvailable) {
215             for (Integer slotId : mIwlanNetworkListenersArray.keySet()) {
216                 if (!isCrossSimCallingCondition(slotId)
217                         && mIwlanRegistered.containsKey(slotId)
218                         && mIwlanRegistered.get(slotId)) {
219                     mWifiAvailable = true;
220                     notifyIwlanNetworkStatus(slotId, false);
221                 }
222             }
223         }
224     }
225 
226     @VisibleForTesting
onWifiDisabling()227     void onWifiDisabling() {
228         mWifiToggleOn = false;
229         if (mWifiAvailable) {
230             mWifiAvailable = false;
231             notifyIwlanNetworkStatus(true);
232         }
233     }
234 
235     @VisibleForTesting
onIwlanServiceStateChanged(int slotId, boolean isRegistered)236     void onIwlanServiceStateChanged(int slotId, boolean isRegistered) {
237         mIwlanRegistered.put(slotId, isRegistered);
238         notifyIwlanNetworkStatus(slotId, false);
239     }
240 
notifyIwlanNetworkStatusToRegister(int slotId, QnsRegistrant r)241     private void notifyIwlanNetworkStatusToRegister(int slotId, QnsRegistrant r) {
242         if (DBG) {
243             Log.d(sLogTag, "notifyIwlanNetworkStatusToRegister");
244         }
245         IwlanAvailabilityInfo info = mLastIwlanAvailabilityInfo.get(slotId);
246         if (info == null) {
247             info = makeIwlanAvailabilityInfo(slotId);
248             mLastIwlanAvailabilityInfo.put(slotId, info);
249         }
250         r.notifyResult(info);
251     }
252 
registerDefaultNetworkCb()253     private void registerDefaultNetworkCb() {
254         if (mDefaultNetworkCallback == null) {
255             mDefaultNetworkCallback = new DefaultNetworkCallback();
256             mConnectivityManager.registerDefaultNetworkCallback(
257                     mDefaultNetworkCallback, mNetCbHandler);
258         }
259     }
260 
unregisterDefaultNetworkCb()261     private void unregisterDefaultNetworkCb() {
262         if (mDefaultNetworkCallback != null) {
263             mConnectivityManager.unregisterNetworkCallback(mDefaultNetworkCallback);
264             mDefaultNetworkCallback = null;
265         }
266     }
267 
close()268     protected void close() {
269         mNetCbHandler.post(this::onClose);
270         mHandlerThread.quitSafely();
271     }
272 
onClose()273     private void onClose() {
274         unregisterDefaultNetworkCb();
275         mLastIwlanAvailabilityInfo.clear();
276         mIwlanNetworkListenersArray.clear();
277         mIwlanRegistered.clear();
278         mCountryDetector.unregisterCountryDetectorCallback(this::updateCountryCode);
279         Log.d(sLogTag, "closed IwlanNetworkStatusTracker");
280     }
281 
registerIwlanNetworksChanged(int slotId, Handler h, int what)282     public void registerIwlanNetworksChanged(int slotId, Handler h, int what) {
283         if (h != null && mHandlerThread.isAlive()) {
284             QnsRegistrant r = new QnsRegistrant(h, what, null);
285             if (mIwlanNetworkListenersArray.get(slotId) == null) {
286                 mIwlanNetworkListenersArray.put(slotId, new QnsRegistrantList());
287             }
288             mIwlanNetworkListenersArray.get(slotId).add(r);
289             IwlanEventHandler handler = mHandlerSparseArray.get(slotId);
290             if (handler != null) {
291                 IwlanAvailabilityInfo lastInfo = mLastIwlanAvailabilityInfo.get(slotId);
292                 IwlanAvailabilityInfo newInfo = makeIwlanAvailabilityInfo(slotId);
293                 if (lastInfo == null || !lastInfo.equals(newInfo)) {
294                     // if the LastIwlanAvailabilityInfo is no more valid, notify to all registrants.
295                     handler.post(() -> notifyIwlanNetworkStatus());
296                 } else {
297                     // if the LastIwlanAvailabilityInfo is valid, notify to only this registrant.
298                     handler.post(() -> notifyIwlanNetworkStatusToRegister(slotId, r));
299                 }
300             }
301         }
302     }
303 
unregisterIwlanNetworksChanged(int slotId, Handler h)304     void unregisterIwlanNetworksChanged(int slotId, Handler h) {
305         if (mIwlanNetworkListenersArray.get(slotId) != null) {
306             mIwlanNetworkListenersArray.get(slotId).remove(h);
307         }
308     }
309 
makeIwlanAvailabilityInfo(int slotId)310     private IwlanAvailabilityInfo makeIwlanAvailabilityInfo(int slotId) {
311         boolean iwlanEnable = false;
312         boolean isCrossWfc = false;
313         boolean isRegistered = false;
314         boolean isBlockIpv6OnlyWifi = false;
315         if (mQnsConfigManagers.contains(slotId)) {
316             isBlockIpv6OnlyWifi = mQnsConfigManagers.get(slotId).blockIpv6OnlyWifi();
317         }
318         LinkProtocolType linkProtocolType = sLinkProtocolType;
319 
320         if (mIwlanRegistered.containsKey(slotId)) {
321             isRegistered = mIwlanRegistered.get(slotId);
322         }
323 
324         if (mWifiAvailable) {
325             boolean blockWifi =
326                     isBlockIpv6OnlyWifi
327                             && ((linkProtocolType == LinkProtocolType.UNKNOWN)
328                                     || (linkProtocolType == LinkProtocolType.IPV6));
329             iwlanEnable = !blockWifi && isRegistered;
330         } else if (isCrossSimCallingCondition(slotId) && isRegistered) {
331             iwlanEnable = true;
332             isCrossWfc = true;
333         }
334         if (DBG) {
335             if (QnsUtils.isCrossSimCallingEnabled(mQnsImsManagers.get(slotId))) {
336                 Log.d(
337                         sLogTag,
338                         "makeIwlanAvailabilityInfo(slot:"
339                                 + slotId
340                                 + ") "
341                                 + "mWifiAvailable:"
342                                 + mWifiAvailable
343                                 + " mConnectedDataSub:"
344                                 + mConnectedDataSub
345                                 + " isRegistered:"
346                                 + isRegistered
347                                 + " subId:"
348                                 + QnsUtils.getSubId(mContext, slotId)
349                                 + " isDDS:"
350                                 + QnsUtils.isDefaultDataSubs(slotId)
351                                 + " iwlanEnable:"
352                                 + iwlanEnable
353                                 + " isCrossWfc:"
354                                 + isCrossWfc);
355             } else {
356                 Log.d(
357                         sLogTag,
358                         "makeIwlanAvailabilityInfo(slot:"
359                                 + slotId
360                                 + ")"
361                                 + " mWifiAvailable:"
362                                 + mWifiAvailable
363                                 + " isRegistered:"
364                                 + isRegistered
365                                 + " iwlanEnable:"
366                                 + iwlanEnable
367                                 + "  isCrossWfc:"
368                                 + isCrossWfc
369                                 + " isBlockIpv6OnlyWifi:"
370                                 + isBlockIpv6OnlyWifi
371                                 + " linkProtocolType:"
372                                 + linkProtocolType);
373             }
374         }
375         return new IwlanAvailabilityInfo(iwlanEnable, isCrossWfc);
376     }
377 
isCrossSimCallingCondition(int slotId)378     private boolean isCrossSimCallingCondition(int slotId) {
379         return QnsUtils.isCrossSimCallingEnabled(mQnsImsManagers.get(slotId))
380                 && QnsUtils.getSubId(mContext, slotId) != mConnectedDataSub
381                 && mConnectedDataSub != INVALID_SUB_ID;
382     }
383 
notifyIwlanNetworkStatus()384     private void notifyIwlanNetworkStatus() {
385         notifyIwlanNetworkStatus(false);
386     }
387 
notifyIwlanNetworkStatus(boolean notifyIwlanDisabled)388     private void notifyIwlanNetworkStatus(boolean notifyIwlanDisabled) {
389         for (Integer slotId : mIwlanNetworkListenersArray.keySet()) {
390             notifyIwlanNetworkStatus(slotId, notifyIwlanDisabled);
391         }
392     }
393 
notifyIwlanNetworkStatus(int slotId, boolean notifyIwlanDisabled)394     private void notifyIwlanNetworkStatus(int slotId, boolean notifyIwlanDisabled) {
395         Log.d(sLogTag, "notifyIwlanNetworkStatus for slot: " + slotId);
396         IwlanAvailabilityInfo info = makeIwlanAvailabilityInfo(slotId);
397         if (!info.getIwlanAvailable() && notifyIwlanDisabled) {
398             Log.d(sLogTag, "setNotifyIwlanDisabled for slot: " + slotId);
399             info.setNotifyIwlanDisabled();
400         }
401         if (!info.equals(mLastIwlanAvailabilityInfo.get(slotId))) {
402             Log.d(sLogTag, "notify updated info for slot: " + slotId);
403             if (mIwlanNetworkListenersArray.get(slotId) != null) {
404                 mIwlanNetworkListenersArray.get(slotId).notifyResult(info);
405             }
406             mLastIwlanAvailabilityInfo.put(slotId, info);
407         }
408     }
409 
410     class IwlanAvailabilityInfo {
411         private boolean mIwlanAvailable = false;
412         private boolean mIsCrossWfc = false;
413         private boolean mNotifyIwlanDisabled = false;
414 
IwlanAvailabilityInfo(boolean iwlanAvailable, boolean crossWfc)415         IwlanAvailabilityInfo(boolean iwlanAvailable, boolean crossWfc) {
416             mIwlanAvailable = iwlanAvailable;
417             mIsCrossWfc = crossWfc;
418         }
419 
420         @VisibleForTesting
setNotifyIwlanDisabled()421         void setNotifyIwlanDisabled() {
422             mNotifyIwlanDisabled = true;
423         }
424 
getIwlanAvailable()425         boolean getIwlanAvailable() {
426             return mIwlanAvailable;
427         }
428 
isCrossWfc()429         boolean isCrossWfc() {
430             return mIsCrossWfc;
431         }
432 
433         @VisibleForTesting
getNotifyIwlanDisabled()434         boolean getNotifyIwlanDisabled() {
435             return mNotifyIwlanDisabled;
436         }
437 
equals(IwlanAvailabilityInfo info)438         boolean equals(IwlanAvailabilityInfo info) {
439             if (info == null) {
440                 Log.d(sLogTag, " equals info is null");
441                 return false;
442             }
443             Log.d(
444                     sLogTag,
445                     "equals() IwlanAvailable: "
446                             + mIwlanAvailable
447                             + "/"
448                             + info.mIwlanAvailable
449                             + " IsCrossWfc: "
450                             + mIsCrossWfc
451                             + "/"
452                             + info.mIsCrossWfc
453                             + " NotifyIwlanDisabled: "
454                             + mNotifyIwlanDisabled
455                             + "/"
456                             + info.mNotifyIwlanDisabled);
457             return (mIwlanAvailable == info.mIwlanAvailable)
458                     && (mIsCrossWfc == info.mIsCrossWfc)
459                     && (mNotifyIwlanDisabled == info.mNotifyIwlanDisabled);
460         }
461     }
462 
463     final class DefaultNetworkCallback extends ConnectivityManager.NetworkCallback {
464         /** Called when the framework connects and has declared a new network ready for use. */
465         @Override
onAvailable(Network network)466         public void onAvailable(Network network) {
467             Log.d(sLogTag, "onAvailable: " + network);
468             if (mConnectivityManager != null) {
469                 NetworkCapabilities nc = mConnectivityManager.getNetworkCapabilities(network);
470                 if (nc != null) {
471                     if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
472                         mWifiToggleOn = true;
473                         mWifiAvailable = true;
474                         mConnectedDataSub = INVALID_SUB_ID;
475                         notifyIwlanNetworkStatus();
476                     } else if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
477                         NetworkSpecifier specifier = nc.getNetworkSpecifier();
478                         TransportInfo transportInfo = nc.getTransportInfo();
479                         if (transportInfo instanceof VcnTransportInfo) {
480                             mConnectedDataSub =
481                                     VcnUtils.getSubIdFromVcnCaps(mConnectivityManager, nc);
482                         } else if (specifier instanceof TelephonyNetworkSpecifier) {
483                             mConnectedDataSub =
484                                     ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
485                         }
486                         mWifiAvailable = false;
487                         notifyIwlanNetworkStatus();
488                     }
489                 }
490             }
491         }
492 
493         /**
494          * Called when the network is about to be lost, typically because there are no outstanding
495          * requests left for it. This may be paired with a {@link
496          * android.net.ConnectivityManager.NetworkCallback#onAvailable} call with the new
497          * replacement network for graceful handover. This method is not guaranteed to be called
498          * before {@link android.net.ConnectivityManager.NetworkCallback#onLost} is called, for
499          * example in case a network is suddenly disconnected.
500          */
501         @Override
onLosing(Network network, int maxMsToLive)502         public void onLosing(Network network, int maxMsToLive) {
503             Log.d(sLogTag, "onLosing: maxMsToLive: " + maxMsToLive + " network: " + network);
504         }
505 
506         /**
507          * Called when a network disconnects or otherwise no longer satisfies this request or *
508          * callback.
509          */
510         @Override
onLost(Network network)511         public void onLost(Network network) {
512             Log.d(sLogTag, "onLost: " + network);
513             if (mWifiAvailable) {
514                 mWifiAvailable = false;
515             }
516             if (mConnectedDataSub != INVALID_SUB_ID) {
517                 mConnectedDataSub = INVALID_SUB_ID;
518             }
519             sLinkProtocolType = LinkProtocolType.UNKNOWN;
520             notifyIwlanNetworkStatus();
521         }
522 
523         /** Called when the network corresponding to this request changes {@link LinkProperties}. */
524         @Override
onLinkPropertiesChanged(Network network, LinkProperties linkProperties)525         public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {
526             Log.d(sLogTag, "onLinkPropertiesChanged: " + linkProperties);
527             if (mWifiAvailable) {
528                 LinkProtocolType prevType = sLinkProtocolType;
529 
530                 checkWifiLinkProtocolType(linkProperties);
531                 if (prevType != LinkProtocolType.IPV6
532                         && sLinkProtocolType == LinkProtocolType.IPV6) {
533                     notifyIwlanNetworkStatus(true);
534                 } else if (prevType != sLinkProtocolType) {
535                     notifyIwlanNetworkStatus();
536                 }
537             }
538         }
539 
540         /** Called when access to the specified network is blocked or unblocked. */
541         @Override
onBlockedStatusChanged(Network network, boolean blocked)542         public void onBlockedStatusChanged(Network network, boolean blocked) {
543             Log.d(sLogTag, "onBlockedStatusChanged: " + " BLOCKED:" + blocked);
544         }
545 
546         @Override
onCapabilitiesChanged( Network network, NetworkCapabilities networkCapabilities)547         public void onCapabilitiesChanged(
548                 Network network, NetworkCapabilities networkCapabilities) {
549             // onCapabilitiesChanged is guaranteed to be called immediately after onAvailable per
550             // API
551             Log.d(sLogTag, "onCapabilitiesChanged: " + network);
552             NetworkCapabilities nc = networkCapabilities;
553             if (nc != null) {
554                 if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
555                     if (!mWifiAvailable && mWifiToggleOn) {
556                         mWifiAvailable = true;
557                         mConnectedDataSub = INVALID_SUB_ID;
558                         notifyIwlanNetworkStatus();
559                     } else {
560                         Log.d(sLogTag, "OnCapability : Wifi Available already true");
561                     }
562                 } else if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
563                     int activeDataSub = INVALID_SUB_ID;
564                     mWifiAvailable = false;
565                     NetworkSpecifier specifier = nc.getNetworkSpecifier();
566                     TransportInfo transportInfo = nc.getTransportInfo();
567                     if (transportInfo instanceof VcnTransportInfo) {
568                         activeDataSub = VcnUtils.getSubIdFromVcnCaps(mConnectivityManager, nc);
569                     } else if (specifier instanceof TelephonyNetworkSpecifier) {
570                         activeDataSub = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
571                     }
572                     if (activeDataSub != INVALID_SUB_ID && activeDataSub != mConnectedDataSub) {
573                         mConnectedDataSub = activeDataSub;
574                         notifyIwlanNetworkStatus();
575                     }
576                 }
577             }
578         }
579     }
580 
checkWifiLinkProtocolType(@onNull LinkProperties linkProperties)581     private void checkWifiLinkProtocolType(@NonNull LinkProperties linkProperties) {
582         boolean hasIpv4 = false;
583         boolean hasIpv6 = false;
584         for (LinkAddress linkAddress : linkProperties.getLinkAddresses()) {
585             InetAddress inetAddress = linkAddress.getAddress();
586             if (inetAddress instanceof Inet4Address) {
587                 hasIpv4 = true;
588             } else if (inetAddress instanceof Inet6Address) {
589                 hasIpv6 = true;
590             }
591         }
592         if (hasIpv4 && hasIpv6) {
593             sLinkProtocolType = LinkProtocolType.IPV4V6;
594         } else if (hasIpv4) {
595             sLinkProtocolType = LinkProtocolType.IPV4;
596         } else if (hasIpv6) {
597             sLinkProtocolType = LinkProtocolType.IPV6;
598         }
599     }
600 
601     /**
602      * This method returns if current country code is outside the home country.
603      *
604      * @return True if it is international roaming, otherwise false.
605      */
isInternationalRoaming(int slotId)606     boolean isInternationalRoaming(int slotId) {
607         boolean isInternationalRoaming = false;
608         String simCountry = mTelephonyManager.createForSubscriptionId(slotId).getSimCountryIso();
609         if (!TextUtils.isEmpty(simCountry) && !TextUtils.isEmpty(mLastKnownCountryCode)) {
610             Log.d(
611                     sLogTag,
612                     "SIM country = " + simCountry + ", current country = " + mLastKnownCountryCode);
613             isInternationalRoaming = !simCountry.equalsIgnoreCase(mLastKnownCountryCode);
614         }
615         return isInternationalRoaming;
616     }
617 
618     /**
619      * This method is to add country listener in order to receive country code from the detector.
620      */
startCountryDetector()621     private void startCountryDetector() {
622         mCountryDetector = mContext.getSystemService(CountryDetector.class);
623         if (mCountryDetector != null) {
624             mCountryDetector.registerCountryDetectorCallback(
625                     new QnsUtils.QnsExecutor(mNetCbHandler), this::updateCountryCode);
626         }
627     }
628 
629     /** This method is to update the last known country code if it is changed. */
updateCountryCode(Country country)630     private void updateCountryCode(Country country) {
631         if (country == null) {
632             return;
633         }
634         if (country.getSource() == Country.COUNTRY_SOURCE_NETWORK
635                 || country.getSource() == Country.COUNTRY_SOURCE_LOCATION) {
636             String newCountryCode = country.getCountryCode();
637             if (!TextUtils.isEmpty(newCountryCode)
638                     && (TextUtils.isEmpty(mLastKnownCountryCode)
639                             || !mLastKnownCountryCode.equalsIgnoreCase(newCountryCode))) {
640                 mLastKnownCountryCode = newCountryCode;
641                 Log.d(sLogTag, "Update the last known country code = " + mLastKnownCountryCode);
642             }
643         }
644     }
645 
646     /**
647      * Dumps the state of {@link QualityMonitor}
648      *
649      * @param pw {@link PrintWriter} to write the state of the object.
650      * @param prefix String to append at start of dumped log.
651      */
dump(PrintWriter pw, String prefix)652     void dump(PrintWriter pw, String prefix) {
653         pw.println(prefix + "------------------------------");
654         pw.println(prefix + "IwlanNetworkStatusTracker:");
655         pw.println(
656                 prefix
657                         + "mWifiAvailable="
658                         + mWifiAvailable
659                         + ", mWifiToggleOn="
660                         + mWifiToggleOn
661                         + ", mConnectedDataSub="
662                         + mConnectedDataSub
663                         + ", mIwlanRegistered="
664                         + mIwlanRegistered);
665         pw.println(prefix + "sLinkProtocolType=" + sLinkProtocolType);
666         pw.println(prefix + "mLastKnownCountryCode=" + mLastKnownCountryCode);
667     }
668 }
669