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