1 /*
2  * Copyright (C) 2022 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.services.telephony.domainselection;
18 
19 import android.annotation.NonNull;
20 import android.content.Context;
21 import android.os.Handler;
22 import android.os.Looper;
23 import android.telephony.AccessNetworkConstants.AccessNetworkType;
24 import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
25 import android.telephony.BarringInfo;
26 import android.telephony.ServiceState;
27 import android.telephony.SubscriptionManager;
28 import android.telephony.ims.ImsException;
29 import android.telephony.ims.ImsManager;
30 import android.telephony.ims.ImsMmTelManager;
31 import android.telephony.ims.ImsReasonInfo;
32 import android.telephony.ims.ImsRegistrationAttributes;
33 import android.telephony.ims.ImsStateCallback;
34 import android.telephony.ims.ImsStateCallback.DisconnectedReason;
35 import android.telephony.ims.RegistrationManager;
36 import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities;
37 import android.telephony.ims.stub.ImsRegistrationImplBase;
38 import android.util.ArraySet;
39 import android.util.IndentingPrintWriter;
40 import android.util.LocalLog;
41 import android.util.Log;
42 
43 import com.android.internal.annotations.Keep;
44 import com.android.internal.annotations.VisibleForTesting;
45 
46 import java.io.PrintWriter;
47 import java.util.Objects;
48 import java.util.Set;
49 
50 /**
51  * A class for tracking the IMS related information like IMS registration state, MMTEL capabilities.
52  * And, it also tracks the {@link ServiceState} and {@link BarringInfo} to identify the current
53  * network state to which the device is attached.
54  */
55 @Keep
56 public class ImsStateTracker {
57     /**
58      * A listener used to be notified of the {@link ServiceState} change.
59      */
60     public interface ServiceStateListener {
61         /**
62          * Called when the {@link ServiceState} is updated.
63          */
onServiceStateUpdated(ServiceState serviceState)64         void onServiceStateUpdated(ServiceState serviceState);
65     }
66 
67     /**
68      * A listener used to be notified of the {@link BarringInfo} change.
69      */
70     public interface BarringInfoListener {
71         /**
72          * Called when the {@link BarringInfo} is updated.
73          */
onBarringInfoUpdated(BarringInfo barringInfo)74         void onBarringInfoUpdated(BarringInfo barringInfo);
75     }
76 
77     /**
78      * A listener used to be notified of the change for MMTEL connection state, IMS registration
79      * state, and MMTEL capabilities.
80      */
81     public interface ImsStateListener {
82         /**
83          * Called when MMTEL feature connection state is changed.
84          */
onImsMmTelFeatureAvailableChanged()85         void onImsMmTelFeatureAvailableChanged();
86 
87         /**
88          * Called when IMS registration state is changed.
89          */
onImsRegistrationStateChanged()90         void onImsRegistrationStateChanged();
91 
92         /**
93          * Called when MMTEL capability is changed - IMS is registered
94          * and the service is currently available over IMS.
95          */
onImsMmTelCapabilitiesChanged()96         void onImsMmTelCapabilitiesChanged();
97     }
98 
99     private static final String TAG = ImsStateTracker.class.getSimpleName();
100     /**
101      * When MMTEL feature connection is unavailable temporarily,
102      * the IMS state will be set to unavailable after waiting for this time.
103      */
104     @VisibleForTesting
105     protected static final long MMTEL_FEATURE_AVAILABLE_WAIT_TIME_MILLIS = 1000; // 1 seconds
106 
107     // Persistent Logging
108     private final LocalLog mEventLog = new LocalLog(30);
109     private final Context mContext;
110     private final int mSlotId;
111     private final Handler mHandler;
112     private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
113 
114     /** For tracking the ServiceState and its related listeners. */
115     private ServiceState mServiceState;
116     private final Set<ServiceStateListener> mServiceStateListeners = new ArraySet<>(2);
117 
118     /** For tracking the BarringInfo and its related listeners. */
119     private BarringInfo mBarringInfo;
120     private final Set<BarringInfoListener> mBarringInfoListeners = new ArraySet<>(2);
121 
122     /** For tracking IMS states and callbacks. */
123     private final Set<ImsStateListener> mImsStateListeners = new ArraySet<>(5);
124     private ImsMmTelManager mMmTelManager;
125     private ImsStateCallback mImsStateCallback;
126     private RegistrationManager.RegistrationCallback mImsRegistrationCallback;
127     private ImsMmTelManager.CapabilityCallback mMmTelCapabilityCallback;
128     /** The availability of MmTelFeature. */
129     private Boolean mMmTelFeatureAvailable;
130     /** The IMS registration state and the network type that performed IMS registration. */
131     private Boolean mImsRegistered;
132     private @RadioAccessNetworkType int mImsAccessNetworkType = AccessNetworkType.UNKNOWN;
133     private Boolean mImsRegisteredOverCrossSim;
134     /** The MMTEL capabilities - Voice, Video, SMS, and Ut. */
135     private MmTelCapabilities mMmTelCapabilities;
136     private final Runnable mMmTelFeatureUnavailableRunnable = new Runnable() {
137         @Override
138         public void run() {
139             setImsStateAsUnavailable();
140             notifyImsMmTelFeatureAvailableChanged();
141         }
142     };
143 
ImsStateTracker(@onNull Context context, int slotId, @NonNull Looper looper)144     public ImsStateTracker(@NonNull Context context, int slotId, @NonNull Looper looper) {
145         mContext = context;
146         mSlotId = slotId;
147         mHandler = new Handler(looper);
148     }
149 
150     /**
151      * Destroys this tracker.
152      */
destroy()153     public void destroy() {
154         stopListeningForImsState();
155         mHandler.removeCallbacksAndMessages(null);
156     }
157 
158     /**
159      * Returns the slot index for this tracker.
160      */
getSlotId()161     public int getSlotId() {
162         return mSlotId;
163     }
164 
165     /**
166      * Returns the current subscription index for this tracker.
167      */
getSubId()168     public int getSubId() {
169         return mSubId;
170     }
171 
172     /**
173      * Returns the Handler instance of this tracker.
174      */
175     @VisibleForTesting
getHandler()176     public @NonNull Handler getHandler() {
177         return mHandler;
178     }
179 
180     /**
181      * Starts monitoring the IMS states with the specified subscription.
182      * This method will be called whenever the subscription index for this tracker is changed.
183      * If the subscription index for this tracker is same as previously set, it will be ignored.
184      *
185      * @param subId The subscription index to be started.
186      */
start(int subId)187     public void start(int subId) {
188         if (mSubId == subId) {
189             if (!SubscriptionManager.isValidSubscriptionId(mSubId)) {
190                 setImsStateAsUnavailable();
191                 return;
192             } else if (mImsStateCallback != null) {
193                 // If start() is called with the same subscription index and the ImsStateCallback
194                 // was already registered, we don't need to unregister and register this callback
195                 // again. So, this request should be ignored if the subscription index is same.
196                 logd("start: ignored for same subscription(" + mSubId + ")");
197                 return;
198             }
199         } else {
200             logi("start: subscription changed from " + mSubId + " to " + subId);
201             mSubId = subId;
202         }
203 
204         stopListeningForImsState();
205         startListeningForImsState();
206     }
207 
208     /**
209      * Updates the service state of the network to which the device is currently attached.
210      * This method should be run on the same thread as the Handler.
211      *
212      * @param serviceState The {@link ServiceState} to be updated.
213      */
updateServiceState(ServiceState serviceState)214     public void updateServiceState(ServiceState serviceState) {
215         mServiceState = serviceState;
216 
217         for (ServiceStateListener listener : mServiceStateListeners) {
218             listener.onServiceStateUpdated(serviceState);
219         }
220     }
221 
222     /**
223      * Adds a listener to be notified of the {@link ServiceState} change.
224      * The newly added listener is notified if the current {@link ServiceState} is present.
225      *
226      * @param listener The listener to be added.
227      */
addServiceStateListener(@onNull ServiceStateListener listener)228     public void addServiceStateListener(@NonNull ServiceStateListener listener) {
229         mServiceStateListeners.add(listener);
230 
231         final ServiceState serviceState = mServiceState;
232         if (serviceState != null) {
233             mHandler.post(() -> notifyServiceStateUpdated(listener, serviceState));
234         }
235     }
236 
237     /**
238      * Removes a listener to be notified of the {@link ServiceState} change.
239      *
240      * @param listener The listener to be removed.
241      */
removeServiceStateListener(@onNull ServiceStateListener listener)242     public void removeServiceStateListener(@NonNull ServiceStateListener listener) {
243         mServiceStateListeners.remove(listener);
244     }
245 
246     /**
247      * Notifies the specified listener of a change to {@link ServiceState}.
248      *
249      * @param listener The listener to be notified.
250      * @param serviceState The {@link ServiceState} to be reported.
251      */
notifyServiceStateUpdated(ServiceStateListener listener, ServiceState serviceState)252     private void notifyServiceStateUpdated(ServiceStateListener listener,
253             ServiceState serviceState) {
254         if (!mServiceStateListeners.contains(listener)) {
255             return;
256         }
257         listener.onServiceStateUpdated(serviceState);
258     }
259 
260     /**
261      * Updates the barring information received from the network to which the device is currently
262      * attached.
263      * This method should be run on the same thread as the Handler.
264      *
265      * @param barringInfo The {@link BarringInfo} to be updated.
266      */
updateBarringInfo(BarringInfo barringInfo)267     public void updateBarringInfo(BarringInfo barringInfo) {
268         mBarringInfo = barringInfo;
269 
270         for (BarringInfoListener listener : mBarringInfoListeners) {
271             listener.onBarringInfoUpdated(barringInfo);
272         }
273     }
274 
275     /**
276      * Adds a listener to be notified of the {@link BarringInfo} change.
277      * The newly added listener is notified if the current {@link BarringInfo} is present.
278      *
279      * @param listener The listener to be added.
280      */
addBarringInfoListener(@onNull BarringInfoListener listener)281     public void addBarringInfoListener(@NonNull BarringInfoListener listener) {
282         mBarringInfoListeners.add(listener);
283 
284         final BarringInfo barringInfo = mBarringInfo;
285         if (barringInfo != null) {
286             mHandler.post(() -> notifyBarringInfoUpdated(listener, barringInfo));
287         }
288     }
289 
290     /**
291      * Removes a listener to be notified of the {@link BarringInfo} change.
292      *
293      * @param listener The listener to be removed.
294      */
removeBarringInfoListener(@onNull BarringInfoListener listener)295     public void removeBarringInfoListener(@NonNull BarringInfoListener listener) {
296         mBarringInfoListeners.remove(listener);
297     }
298 
299     /**
300      * Notifies the specified listener of a change to {@link BarringInfo}.
301      *
302      * @param listener The listener to be notified.
303      * @param barringInfo The {@link BarringInfo} to be reported.
304      */
notifyBarringInfoUpdated(BarringInfoListener listener, BarringInfo barringInfo)305     private void notifyBarringInfoUpdated(BarringInfoListener listener, BarringInfo barringInfo) {
306         if (!mBarringInfoListeners.contains(listener)) {
307             return;
308         }
309         listener.onBarringInfoUpdated(barringInfo);
310     }
311 
312     /**
313      * Adds a listener to be notified of the IMS state change.
314      * If each state was already received from the IMS service, the newly added listener
315      * is notified once.
316      *
317      * @param listener The listener to be added.
318      */
addImsStateListener(@onNull ImsStateListener listener)319     public void addImsStateListener(@NonNull ImsStateListener listener) {
320         mImsStateListeners.add(listener);
321         mHandler.post(() -> notifyImsStateChangeIfValid(listener));
322     }
323 
324     /**
325      * Removes a listener to be notified of the IMS state change.
326      *
327      * @param listener The listener to be removed.
328      */
removeImsStateListener(@onNull ImsStateListener listener)329     public void removeImsStateListener(@NonNull ImsStateListener listener) {
330         mImsStateListeners.remove(listener);
331     }
332 
333     /**
334      * Returns {@code true} if all IMS states are ready, {@code false} otherwise.
335      */
336     @VisibleForTesting
isImsStateReady()337     public boolean isImsStateReady() {
338         return mMmTelFeatureAvailable != null
339                 && mImsRegistered != null
340                 && mMmTelCapabilities != null;
341     }
342 
343     /**
344      * Returns {@code true} if MMTEL feature connection is available, {@code false} otherwise.
345      */
isMmTelFeatureAvailable()346     public boolean isMmTelFeatureAvailable() {
347         return mMmTelFeatureAvailable != null && mMmTelFeatureAvailable;
348     }
349 
350     /**
351      * Returns {@code true} if IMS is registered, {@code false} otherwise.
352      */
isImsRegistered()353     public boolean isImsRegistered() {
354         return mImsRegistered != null && mImsRegistered;
355     }
356 
357     /**
358      * Returns {@code true} if IMS is registered over Wi-Fi (IWLAN), {@code false} otherwise.
359      */
isImsRegisteredOverWlan()360     public boolean isImsRegisteredOverWlan() {
361         return mImsAccessNetworkType == AccessNetworkType.IWLAN;
362     }
363 
364     /**
365      * Returns {@code true} if IMS is registered over the mobile data of another subscription.
366      */
isImsRegisteredOverCrossSim()367     public boolean isImsRegisteredOverCrossSim() {
368         return mImsRegisteredOverCrossSim != null && mImsRegisteredOverCrossSim;
369     }
370 
371     /**
372      * Returns {@code true} if IMS voice call is capable, {@code false} otherwise.
373      */
isImsVoiceCapable()374     public boolean isImsVoiceCapable() {
375         return mMmTelCapabilities != null
376                 && mMmTelCapabilities.isCapable(MmTelCapabilities.CAPABILITY_TYPE_VOICE);
377     }
378 
379     /**
380      * Returns {@code true} if IMS video call is capable, {@code false} otherwise.
381      */
isImsVideoCapable()382     public boolean isImsVideoCapable() {
383         return mMmTelCapabilities != null
384                 && mMmTelCapabilities.isCapable(MmTelCapabilities.CAPABILITY_TYPE_VIDEO);
385     }
386 
387     /**
388      * Returns {@code true} if IMS SMS is capable, {@code false} otherwise.
389      */
isImsSmsCapable()390     public boolean isImsSmsCapable() {
391         return mMmTelCapabilities != null
392                 && mMmTelCapabilities.isCapable(MmTelCapabilities.CAPABILITY_TYPE_SMS);
393     }
394 
395     /**
396      * Returns {@code true} if IMS UT is capable, {@code false} otherwise.
397      */
isImsUtCapable()398     public boolean isImsUtCapable() {
399         return mMmTelCapabilities != null
400                 && mMmTelCapabilities.isCapable(MmTelCapabilities.CAPABILITY_TYPE_UT);
401     }
402 
403     /**
404      * Returns the access network type to which IMS is registered.
405      */
getImsAccessNetworkType()406     public @RadioAccessNetworkType int getImsAccessNetworkType() {
407         return mImsAccessNetworkType;
408     }
409 
410     /**
411      * Sets the IMS states to the initial values.
412      */
initImsState()413     private void initImsState() {
414         mMmTelFeatureAvailable = null;
415         mImsRegistered = null;
416         mImsAccessNetworkType = AccessNetworkType.UNKNOWN;
417         mImsRegisteredOverCrossSim = null;
418         mMmTelCapabilities = null;
419     }
420 
421     /**
422      * Sets the IMS states to unavailable to notify the readiness of the IMS state
423      * when the subscription is not valid.
424      */
setImsStateAsUnavailable()425     private void setImsStateAsUnavailable() {
426         logd("setImsStateAsUnavailable");
427         setMmTelFeatureAvailable(false);
428         setImsRegistered(false);
429         setImsAccessNetworkType(AccessNetworkType.UNKNOWN);
430         setImsRegisteredOverCrossSim(false);
431         setMmTelCapabilities(new MmTelCapabilities());
432     }
433 
setMmTelFeatureAvailable(boolean available)434     private void setMmTelFeatureAvailable(boolean available) {
435         if (!Objects.equals(mMmTelFeatureAvailable, Boolean.valueOf(available))) {
436             logi("setMmTelFeatureAvailable: " + mMmTelFeatureAvailable + " >> " + available);
437             mMmTelFeatureAvailable = Boolean.valueOf(available);
438         }
439     }
440 
setImsRegistered(boolean registered)441     private void setImsRegistered(boolean registered) {
442         if (!Objects.equals(mImsRegistered, Boolean.valueOf(registered))) {
443             logi("setImsRegistered: " + mImsRegistered + " >> " + registered);
444             mImsRegistered = Boolean.valueOf(registered);
445         }
446     }
447 
setImsAccessNetworkType(int accessNetworkType)448     private void setImsAccessNetworkType(int accessNetworkType) {
449         if (mImsAccessNetworkType != accessNetworkType) {
450             logi("setImsAccessNetworkType: " + accessNetworkTypeToString(mImsAccessNetworkType)
451                     + " >> " + accessNetworkTypeToString(accessNetworkType));
452             mImsAccessNetworkType = accessNetworkType;
453         }
454     }
455 
setMmTelCapabilities(@onNull MmTelCapabilities capabilities)456     private void setMmTelCapabilities(@NonNull MmTelCapabilities capabilities) {
457         if (!Objects.equals(mMmTelCapabilities, capabilities)) {
458             logi("MMTEL capabilities: " + mMmTelCapabilities + " >> " + capabilities);
459             mMmTelCapabilities = capabilities;
460         }
461     }
462 
setImsRegisteredOverCrossSim(boolean crossSim)463     private void setImsRegisteredOverCrossSim(boolean crossSim) {
464         if (!Objects.equals(mImsRegisteredOverCrossSim, Boolean.valueOf(crossSim))) {
465             logi("setImsRegisteredOverCrossSim: " + mImsRegisteredOverCrossSim + " >> " + crossSim);
466             mImsRegisteredOverCrossSim = Boolean.valueOf(crossSim);
467         }
468     }
469 
470     /**
471      * Notifies the specified listener of the current IMS state if it's valid.
472      *
473      * @param listener The {@link ImsStateListener} to be notified.
474      */
notifyImsStateChangeIfValid(@onNull ImsStateListener listener)475     private void notifyImsStateChangeIfValid(@NonNull ImsStateListener listener) {
476         if (!mImsStateListeners.contains(listener)) {
477             return;
478         }
479 
480         if (mMmTelFeatureAvailable != null) {
481             listener.onImsMmTelFeatureAvailableChanged();
482         }
483 
484         if (mImsRegistered != null) {
485             listener.onImsRegistrationStateChanged();
486         }
487 
488         if (mMmTelCapabilities != null) {
489             listener.onImsMmTelCapabilitiesChanged();
490         }
491     }
492 
493     /**
494      * Notifies the application that MMTEL feature connection state is changed.
495      */
notifyImsMmTelFeatureAvailableChanged()496     private void notifyImsMmTelFeatureAvailableChanged() {
497         for (ImsStateListener l : mImsStateListeners) {
498             l.onImsMmTelFeatureAvailableChanged();
499         }
500     }
501 
502     /**
503      * Notifies the application that IMS registration state is changed.
504      */
notifyImsRegistrationStateChanged()505     private void notifyImsRegistrationStateChanged() {
506         logi("ImsState: " + imsStateToString());
507         for (ImsStateListener l : mImsStateListeners) {
508             l.onImsRegistrationStateChanged();
509         }
510     }
511 
512     /**
513      * Notifies the application that MMTEL capabilities is changed.
514      */
notifyImsMmTelCapabilitiesChanged()515     private void notifyImsMmTelCapabilitiesChanged() {
516         logi("ImsState: " + imsStateToString());
517         for (ImsStateListener l : mImsStateListeners) {
518             l.onImsMmTelCapabilitiesChanged();
519         }
520     }
521 
522     /**
523      * Called when MMTEL feature connection state is available.
524      */
onMmTelFeatureAvailable()525     private void onMmTelFeatureAvailable() {
526         logd("onMmTelFeatureAvailable");
527         mHandler.removeCallbacks(mMmTelFeatureUnavailableRunnable);
528         setMmTelFeatureAvailable(true);
529         registerImsRegistrationCallback();
530         registerMmTelCapabilityCallback();
531         notifyImsMmTelFeatureAvailableChanged();
532     }
533 
534     /**
535      * Called when MMTEL feature connection state is unavailable.
536      */
onMmTelFeatureUnavailable(@isconnectedReason int reason)537     private void onMmTelFeatureUnavailable(@DisconnectedReason int reason) {
538         logd("onMmTelFeatureUnavailable: reason=" + disconnectedCauseToString(reason));
539 
540         if (reason == ImsStateCallback.REASON_UNKNOWN_TEMPORARY_ERROR
541                 || reason == ImsStateCallback.REASON_IMS_SERVICE_NOT_READY) {
542             // Wait for onAvailable for some times and
543             // if it's not available, the IMS state will be set to unavailable.
544             initImsState();
545             setMmTelFeatureAvailable(false);
546             mHandler.postDelayed(mMmTelFeatureUnavailableRunnable,
547                     MMTEL_FEATURE_AVAILABLE_WAIT_TIME_MILLIS);
548         } else if (reason == ImsStateCallback.REASON_UNKNOWN_PERMANENT_ERROR
549                 || reason == ImsStateCallback.REASON_NO_IMS_SERVICE_CONFIGURED) {
550             // Permanently blocked for this subscription.
551             setImsStateAsUnavailable();
552             notifyImsMmTelFeatureAvailableChanged();
553         } else if (reason == ImsStateCallback.REASON_IMS_SERVICE_DISCONNECTED) {
554             // Wait for onAvailable for some times and
555             // if it's not available, the IMS state will be set to unavailable.
556             initImsState();
557             setMmTelFeatureAvailable(false);
558             unregisterImsRegistrationCallback();
559             unregisterMmTelCapabilityCallback();
560             mHandler.postDelayed(mMmTelFeatureUnavailableRunnable,
561                     MMTEL_FEATURE_AVAILABLE_WAIT_TIME_MILLIS);
562         } else if (reason == ImsStateCallback.REASON_SUBSCRIPTION_INACTIVE) {
563             // The {@link TelephonyDomainSelectionService} will call ImsStateTracker#start
564             // when the subscription changes to register new callbacks.
565             setImsStateAsUnavailable();
566             unregisterImsRegistrationCallback();
567             unregisterMmTelCapabilityCallback();
568             // ImsStateCallback has already been removed after calling onUnavailable.
569             mImsStateCallback = null;
570             notifyImsMmTelFeatureAvailableChanged();
571         } else {
572             logw("onMmTelFeatureUnavailable: unexpected reason=" + reason);
573         }
574     }
575 
576     /**
577      * Called when IMS is registered to the IMS network.
578      */
onImsRegistered(@onNull ImsRegistrationAttributes attributes)579     private void onImsRegistered(@NonNull ImsRegistrationAttributes attributes) {
580         logd("onImsRegistered: " + attributes);
581 
582         setImsRegistered(true);
583         setImsAccessNetworkType(
584                 imsRegTechToAccessNetworkType(attributes.getRegistrationTechnology()));
585         setImsRegisteredOverCrossSim(attributes.getRegistrationTechnology()
586                 == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM);
587         notifyImsRegistrationStateChanged();
588     }
589 
590     /**
591      * Called when IMS is unregistered from the IMS network.
592      */
onImsUnregistered(@onNull ImsReasonInfo info)593     private void onImsUnregistered(@NonNull ImsReasonInfo info) {
594         logd("onImsUnregistered: " + info);
595         setImsRegistered(false);
596         setImsAccessNetworkType(AccessNetworkType.UNKNOWN);
597         setImsRegisteredOverCrossSim(false);
598         setMmTelCapabilities(new MmTelCapabilities());
599         notifyImsRegistrationStateChanged();
600     }
601 
602     /**
603      * Called when MMTEL capability is changed - IMS is registered
604      * and the service is currently available over IMS.
605      */
onMmTelCapabilitiesChanged(@onNull MmTelCapabilities capabilities)606     private void onMmTelCapabilitiesChanged(@NonNull MmTelCapabilities capabilities) {
607         logd("onMmTelCapabilitiesChanged: " + capabilities);
608         setMmTelCapabilities(capabilities);
609         notifyImsMmTelCapabilitiesChanged();
610     }
611 
612     /**
613      * Starts listening to monitor the IMS states -
614      * connection state, IMS registration state, and MMTEL capabilities.
615      */
startListeningForImsState()616     private void startListeningForImsState() {
617         if (!SubscriptionManager.isValidSubscriptionId(getSubId())) {
618             setImsStateAsUnavailable();
619             return;
620         }
621 
622         ImsManager imsMngr = mContext.getSystemService(ImsManager.class);
623         mMmTelManager = imsMngr.getImsMmTelManager(getSubId());
624         initImsState();
625         registerImsStateCallback();
626     }
627 
628     /**
629      * Stops listening to monitor the IMS states -
630      * connection state, IMS registration state, and MMTEL capabilities.
631      */
stopListeningForImsState()632     private void stopListeningForImsState() {
633         mHandler.removeCallbacks(mMmTelFeatureUnavailableRunnable);
634 
635         if (mMmTelManager != null) {
636             unregisterMmTelCapabilityCallback();
637             unregisterImsRegistrationCallback();
638             unregisterImsStateCallback();
639             mMmTelManager = null;
640         }
641     }
642 
registerImsStateCallback()643     private void registerImsStateCallback() {
644         if (mImsStateCallback != null) {
645             loge("ImsStateCallback is already registered for sub-" + getSubId());
646             return;
647         }
648         /**
649          * Listens to the IMS connection state change.
650          */
651         mImsStateCallback = new ImsStateCallback() {
652             @Override
653             public void onUnavailable(@DisconnectedReason int reason) {
654                 onMmTelFeatureUnavailable(reason);
655             }
656 
657             @Override
658             public void onAvailable() {
659                 onMmTelFeatureAvailable();
660             }
661 
662             @Override
663             public void onError() {
664                 // This case will not be happened because this domain selection service
665                 // is running on the Telephony service.
666             }
667         };
668 
669         try {
670             mMmTelManager.registerImsStateCallback(mHandler::post, mImsStateCallback);
671         } catch (ImsException e) {
672             loge("Exception when registering ImsStateCallback: " + e);
673             mImsStateCallback = null;
674         }
675     }
676 
unregisterImsStateCallback()677     private void unregisterImsStateCallback() {
678         if (mImsStateCallback != null) {
679             try {
680                 mMmTelManager.unregisterImsStateCallback(mImsStateCallback);
681             }  catch (Exception ignored) {
682                 // Ignore the runtime exception while unregistering callback.
683                 logd("Exception when unregistering ImsStateCallback: " + ignored);
684             }
685             mImsStateCallback = null;
686         }
687     }
688 
registerImsRegistrationCallback()689     private void registerImsRegistrationCallback() {
690         if (mImsRegistrationCallback != null) {
691             logd("RegistrationCallback is already registered for sub-" + getSubId());
692             return;
693         }
694         /**
695          * Listens to the IMS registration state change.
696          */
697         mImsRegistrationCallback = new RegistrationManager.RegistrationCallback() {
698             @Override
699             public void onRegistered(@NonNull ImsRegistrationAttributes attributes) {
700                 onImsRegistered(attributes);
701             }
702 
703             @Override
704             public void onUnregistered(@NonNull ImsReasonInfo info) {
705                 onImsUnregistered(info);
706             }
707         };
708 
709         try {
710             mMmTelManager.registerImsRegistrationCallback(mHandler::post, mImsRegistrationCallback);
711         } catch (ImsException e) {
712             loge("Exception when registering RegistrationCallback: " + e);
713             mImsRegistrationCallback = null;
714         }
715     }
716 
unregisterImsRegistrationCallback()717     private void unregisterImsRegistrationCallback() {
718         if (mImsRegistrationCallback != null) {
719             try {
720                 mMmTelManager.unregisterImsRegistrationCallback(mImsRegistrationCallback);
721             }  catch (Exception ignored) {
722                 // Ignore the runtime exception while unregistering callback.
723                 logd("Exception when unregistering RegistrationCallback: " + ignored);
724             }
725             mImsRegistrationCallback = null;
726         }
727     }
728 
registerMmTelCapabilityCallback()729     private void registerMmTelCapabilityCallback() {
730         if (mMmTelCapabilityCallback != null) {
731             logd("CapabilityCallback is already registered for sub-" + getSubId());
732             return;
733         }
734         /**
735          * Listens to the MmTel feature capabilities change.
736          */
737         mMmTelCapabilityCallback = new ImsMmTelManager.CapabilityCallback() {
738             @Override
739             public void onCapabilitiesStatusChanged(@NonNull MmTelCapabilities capabilities) {
740                 onMmTelCapabilitiesChanged(capabilities);
741             }
742         };
743 
744         try {
745             mMmTelManager.registerMmTelCapabilityCallback(mHandler::post, mMmTelCapabilityCallback);
746         } catch (ImsException e) {
747             loge("Exception when registering CapabilityCallback: " + e);
748             mMmTelCapabilityCallback = null;
749         }
750     }
751 
unregisterMmTelCapabilityCallback()752     private void unregisterMmTelCapabilityCallback() {
753         if (mMmTelCapabilityCallback != null) {
754             try {
755                 mMmTelManager.unregisterMmTelCapabilityCallback(mMmTelCapabilityCallback);
756             } catch (Exception ignored) {
757                 // Ignore the runtime exception while unregistering callback.
758                 logd("Exception when unregistering CapabilityCallback: " + ignored);
759             }
760             mMmTelCapabilityCallback = null;
761         }
762     }
763 
764     /** Returns a string representation of IMS states. */
imsStateToString()765     public String imsStateToString() {
766         StringBuilder sb = new StringBuilder("{ ");
767         sb.append("MMTEL: featureAvailable=").append(booleanToString(mMmTelFeatureAvailable));
768         sb.append(", registered=").append(booleanToString(mImsRegistered));
769         sb.append(", accessNetworkType=").append(accessNetworkTypeToString(mImsAccessNetworkType));
770         sb.append(", capabilities=").append(mmTelCapabilitiesToString(mMmTelCapabilities));
771         sb.append(" }");
772         return sb.toString();
773     }
774 
accessNetworkTypeToString( @adioAccessNetworkType int accessNetworkType)775     protected static String accessNetworkTypeToString(
776             @RadioAccessNetworkType int accessNetworkType) {
777         switch (accessNetworkType) {
778             case AccessNetworkType.UNKNOWN: return "UNKNOWN";
779             case AccessNetworkType.GERAN: return "GERAN";
780             case AccessNetworkType.UTRAN: return "UTRAN";
781             case AccessNetworkType.EUTRAN: return "EUTRAN";
782             case AccessNetworkType.CDMA2000: return "CDMA2000";
783             case AccessNetworkType.IWLAN: return "IWLAN";
784             case AccessNetworkType.NGRAN: return "NGRAN";
785             default: return Integer.toString(accessNetworkType);
786         }
787     }
788 
789     /** Converts the IMS registration technology to the access network type. */
imsRegTechToAccessNetworkType( @msRegistrationImplBase.ImsRegistrationTech int imsRegTech)790     private static @RadioAccessNetworkType int imsRegTechToAccessNetworkType(
791             @ImsRegistrationImplBase.ImsRegistrationTech int imsRegTech) {
792         switch (imsRegTech) {
793             case ImsRegistrationImplBase.REGISTRATION_TECH_LTE:
794                 return AccessNetworkType.EUTRAN;
795             case ImsRegistrationImplBase.REGISTRATION_TECH_NR:
796                 return AccessNetworkType.NGRAN;
797             case ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN:
798             case ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM:
799                 return AccessNetworkType.IWLAN;
800             default:
801                 return AccessNetworkType.UNKNOWN;
802         }
803     }
804 
booleanToString(Boolean b)805     private static String booleanToString(Boolean b) {
806         return b == null ? "null" : b.toString();
807     }
808 
mmTelCapabilitiesToString(MmTelCapabilities c)809     private static String mmTelCapabilitiesToString(MmTelCapabilities c) {
810         if (c == null) {
811             return "null";
812         }
813         StringBuilder sb = new StringBuilder("[");
814         sb.append("voice=").append(c.isCapable(MmTelCapabilities.CAPABILITY_TYPE_VOICE));
815         sb.append(", video=").append(c.isCapable(MmTelCapabilities.CAPABILITY_TYPE_VIDEO));
816         sb.append(", ut=").append(c.isCapable(MmTelCapabilities.CAPABILITY_TYPE_UT));
817         sb.append(", sms=").append(c.isCapable(MmTelCapabilities.CAPABILITY_TYPE_SMS));
818         sb.append("]");
819         return sb.toString();
820     }
821 
disconnectedCauseToString(@isconnectedReason int reason)822     private static String disconnectedCauseToString(@DisconnectedReason int reason) {
823         switch (reason) {
824             case ImsStateCallback.REASON_UNKNOWN_TEMPORARY_ERROR:
825                 return "UNKNOWN_TEMPORARY_ERROR";
826             case ImsStateCallback.REASON_UNKNOWN_PERMANENT_ERROR:
827                 return "UNKNOWN_PERMANENT_ERROR";
828             case ImsStateCallback.REASON_IMS_SERVICE_DISCONNECTED:
829                 return "IMS_SERVICE_DISCONNECTED";
830             case ImsStateCallback.REASON_NO_IMS_SERVICE_CONFIGURED:
831                 return "NO_IMS_SERVICE_CONFIGURED";
832             case ImsStateCallback.REASON_SUBSCRIPTION_INACTIVE:
833                 return "SUBSCRIPTION_INACTIVE";
834             case ImsStateCallback.REASON_IMS_SERVICE_NOT_READY:
835                 return "IMS_SERVICE_NOT_READY";
836             default:
837                 return Integer.toString(reason);
838         }
839     }
840 
841     /**
842      * Dumps this instance into a readable format for dumpsys usage.
843      */
dump(@onNull PrintWriter pw)844     public void dump(@NonNull PrintWriter pw) {
845         IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
846         ipw.println("ImsStateTracker:");
847         ipw.increaseIndent();
848         ipw.println("SlotId: " + getSlotId());
849         ipw.println("SubId: " + getSubId());
850         ipw.println("ServiceState: " + mServiceState);
851         ipw.println("BarringInfo: " + mBarringInfo);
852         ipw.println("ImsState: " + imsStateToString());
853         ipw.println("Event Log:");
854         ipw.increaseIndent();
855         mEventLog.dump(ipw);
856         ipw.decreaseIndent();
857         ipw.decreaseIndent();
858     }
859 
logd(String s)860     private void logd(String s) {
861         Log.d(TAG, "[" + getSlotId() + "|" + getSubId() + "] " + s);
862     }
863 
logi(String s)864     private void logi(String s) {
865         Log.i(TAG, "[" + getSlotId() + "|" + getSubId() + "] " + s);
866         mEventLog.log("[" + getSlotId() + "|" + getSubId() + "] " + s);
867     }
868 
loge(String s)869     private void loge(String s) {
870         Log.e(TAG, "[" + getSlotId() + "|" + getSubId() + "] " + s);
871         mEventLog.log("[" + getSlotId() + "|" + getSubId() + "] " + s);
872     }
873 
logw(String s)874     private void logw(String s) {
875         Log.w(TAG, "[" + getSlotId() + "|" + getSubId() + "] " + s);
876         mEventLog.log("[" + getSlotId() + "|" + getSubId() + "] " + s);
877     }
878 }
879