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.phone;
18 
19 import static android.telephony.ims.ImsStateCallback.REASON_IMS_SERVICE_DISCONNECTED;
20 import static android.telephony.ims.ImsStateCallback.REASON_IMS_SERVICE_NOT_READY;
21 import static android.telephony.ims.ImsStateCallback.REASON_NO_IMS_SERVICE_CONFIGURED;
22 import static android.telephony.ims.ImsStateCallback.REASON_SUBSCRIPTION_INACTIVE;
23 import static android.telephony.ims.ImsStateCallback.REASON_UNKNOWN_PERMANENT_ERROR;
24 import static android.telephony.ims.ImsStateCallback.REASON_UNKNOWN_TEMPORARY_ERROR;
25 import static android.telephony.ims.feature.ImsFeature.FEATURE_MMTEL;
26 import static android.telephony.ims.feature.ImsFeature.FEATURE_RCS;
27 import static android.telephony.ims.feature.ImsFeature.STATE_READY;
28 import static android.telephony.ims.feature.ImsFeature.STATE_UNAVAILABLE;
29 
30 import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED;
31 import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_IMS_UNSUPPORTED;
32 import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_NOT_READY;
33 import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE;
34 
35 import android.content.BroadcastReceiver;
36 import android.content.Context;
37 import android.content.Intent;
38 import android.content.IntentFilter;
39 import android.os.AsyncResult;
40 import android.os.Bundle;
41 import android.os.Handler;
42 import android.os.HandlerThread;
43 import android.os.IBinder;
44 import android.os.Looper;
45 import android.os.Message;
46 import android.telephony.CarrierConfigManager;
47 import android.telephony.SubscriptionManager;
48 import android.telephony.TelephonyRegistryManager;
49 import android.telephony.ims.feature.ImsFeature;
50 import android.util.LocalLog;
51 import android.util.Log;
52 import android.util.SparseArray;
53 
54 import com.android.ims.FeatureConnector;
55 import com.android.ims.ImsManager;
56 import com.android.ims.RcsFeatureManager;
57 import com.android.internal.annotations.VisibleForTesting;
58 import com.android.internal.telephony.IImsStateCallback;
59 import com.android.internal.telephony.Phone;
60 import com.android.internal.telephony.PhoneConfigurationManager;
61 import com.android.internal.telephony.PhoneFactory;
62 import com.android.internal.telephony.ims.ImsResolver;
63 import com.android.internal.telephony.util.HandlerExecutor;
64 import com.android.internal.util.IndentingPrintWriter;
65 import com.android.services.telephony.rcs.RcsFeatureController;
66 import com.android.telephony.Rlog;
67 
68 import java.util.ArrayList;
69 import java.util.Arrays;
70 import java.util.HashMap;
71 import java.util.concurrent.ConcurrentHashMap;
72 import java.util.concurrent.Executor;
73 
74 /**
75  * Implementation of the controller managing {@link ImsStateCallback}s
76  */
77 public class ImsStateCallbackController {
78     private static final String TAG = "ImsStateCallbackController";
79     private static final boolean VDBG = false;
80     private static final int LOG_SIZE = 50;
81 
82     /**
83      * Create a FeatureConnector for this class to use to connect to an ImsManager.
84      */
85     @VisibleForTesting
86     public interface MmTelFeatureConnectorFactory {
87         /**
88          * Create a FeatureConnector for this class to use to connect to an ImsManager.
89          * @param listener will receive ImsManager instance.
90          * @param executor that the Listener callbacks will be called on.
91          * @return A FeatureConnector
92          */
create(Context context, int slotId, String logPrefix, FeatureConnector.Listener<ImsManager> listener, Executor executor)93         FeatureConnector<ImsManager> create(Context context, int slotId,
94                 String logPrefix, FeatureConnector.Listener<ImsManager> listener,
95                 Executor executor);
96     }
97 
98     /**
99      * Create a FeatureConnector for this class to use to connect to an RcsFeatureManager.
100      */
101     @VisibleForTesting
102     public interface RcsFeatureConnectorFactory {
103         /**
104          * Create a FeatureConnector for this class to use to connect to an RcsFeatureManager.
105          * @param listener will receive RcsFeatureManager instance.
106          * @param executor that the Listener callbacks will be called on.
107          * @return A FeatureConnector
108          */
create(Context context, int slotId, FeatureConnector.Listener<RcsFeatureManager> listener, Executor executor, String logPrefix)109         FeatureConnector<RcsFeatureManager> create(Context context, int slotId,
110                 FeatureConnector.Listener<RcsFeatureManager> listener,
111                 Executor executor, String logPrefix);
112     }
113 
114     /** Indicates that the state is not valid, used in ExternalRcsFeatureState only */
115     private static final int STATE_UNKNOWN = -1;
116 
117     /** The unavailable reason of ImsFeature is not initialized */
118     private static final int NOT_INITIALIZED = -1;
119     /** The ImsFeature is available. */
120     private static final int AVAILABLE = 0;
121 
122     private static final int EVENT_SUB_CHANGED = 1;
123     private static final int EVENT_REGISTER_CALLBACK = 2;
124     private static final int EVENT_UNREGISTER_CALLBACK = 3;
125     private static final int EVENT_CARRIER_CONFIG_CHANGED = 4;
126     private static final int EVENT_EXTERNAL_RCS_STATE_CHANGED = 5;
127     private static final int EVENT_MSIM_CONFIGURATION_CHANGE = 6;
128 
129     private static ImsStateCallbackController sInstance;
130     private static final LocalLog sLocalLog = new LocalLog(LOG_SIZE);
131 
132     /**
133      * get the instance
134      */
getInstance()135     public static ImsStateCallbackController getInstance() {
136         synchronized (ImsStateCallbackController.class) {
137             return sInstance;
138         }
139     }
140 
141     private final PhoneGlobals mApp;
142     private final Handler mHandler;
143     private final ImsResolver mImsResolver;
144     private final SparseArray<MmTelFeatureListener> mMmTelFeatureListeners = new SparseArray<>();
145     private final SparseArray<RcsFeatureListener> mRcsFeatureListeners = new SparseArray<>();
146 
147     // Container to store ImsManager instance by subId
148     private final ConcurrentHashMap<Integer, ImsManager> mSubIdToImsManagerCache =
149             new ConcurrentHashMap<>();
150 
151     private final SubscriptionManager mSubscriptionManager;
152     private final TelephonyRegistryManager mTelephonyRegistryManager;
153     private MmTelFeatureConnectorFactory mMmTelFeatureFactory;
154     private RcsFeatureConnectorFactory mRcsFeatureFactory;
155 
156     private HashMap<IBinder, CallbackWrapper> mWrappers = new HashMap<>();
157 
158     private final Object mDumpLock = new Object();
159 
160     private int mNumSlots;
161 
162     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
163         @Override
164         public void onReceive(Context context, Intent intent) {
165             if (intent == null) {
166                 return;
167             }
168             if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
169                 Bundle bundle = intent.getExtras();
170                 if (bundle == null) {
171                     return;
172                 }
173                 int slotId = bundle.getInt(CarrierConfigManager.EXTRA_SLOT_INDEX,
174                         SubscriptionManager.INVALID_PHONE_INDEX);
175                 int subId = bundle.getInt(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
176                         SubscriptionManager.INVALID_SUBSCRIPTION_ID);
177 
178                 if (slotId <= SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
179                     loge("onReceive ACTION_CARRIER_CONFIG_CHANGED invalid slotId");
180                     return;
181                 }
182 
183                 if (subId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
184                     loge("onReceive ACTION_CARRIER_CONFIG_CHANGED invalid subId");
185                     //subscription changed will be notified by mSubChangedListener
186                     return;
187                 }
188 
189                 notifyCarrierConfigChanged(slotId);
190             }
191         }
192     };
193 
194     private final SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener =
195             new SubscriptionManager.OnSubscriptionsChangedListener() {
196         @Override
197         public void onSubscriptionsChanged() {
198             if (!mHandler.hasMessages(EVENT_SUB_CHANGED)) {
199                 mHandler.sendEmptyMessage(EVENT_SUB_CHANGED);
200             }
201         }
202     };
203 
204     private final class MyHandler extends Handler {
MyHandler(Looper looper)205         MyHandler(Looper looper) {
206             super(looper);
207         }
208 
209         @Override
handleMessage(Message msg)210         public void handleMessage(Message msg) {
211             if (VDBG) logv("handleMessage: " + msg);
212             synchronized (mDumpLock) {
213                 switch (msg.what) {
214                     case EVENT_SUB_CHANGED:
215                         onSubChanged();
216                         break;
217 
218                     case EVENT_REGISTER_CALLBACK:
219                         onRegisterCallback((ImsStateCallbackController.CallbackWrapper) msg.obj);
220                         break;
221 
222                     case EVENT_UNREGISTER_CALLBACK:
223                         onUnregisterCallback((IImsStateCallback) msg.obj);
224                         break;
225 
226                     case EVENT_CARRIER_CONFIG_CHANGED:
227                         onCarrierConfigChanged(msg.arg1);
228                         break;
229 
230                     case EVENT_EXTERNAL_RCS_STATE_CHANGED:
231                         if (msg.obj == null) break;
232                         onExternalRcsStateChanged((ExternalRcsFeatureState) msg.obj);
233                         break;
234 
235                     case EVENT_MSIM_CONFIGURATION_CHANGE:
236                         AsyncResult result = (AsyncResult) msg.obj;
237                         Integer numSlots = (Integer) result.result;
238                         if (numSlots == null) {
239                             Log.w(TAG, "msim config change with null num slots");
240                             break;
241                         }
242                         updateFeatureControllerSize(numSlots);
243                         break;
244 
245                     default:
246                         loge("Unhandled event " + msg.what);
247                 }
248             }
249         }
250     }
251 
252     private final class MmTelFeatureListener implements FeatureConnector.Listener<ImsManager> {
253         private FeatureConnector<ImsManager> mConnector;
254         private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
255         private int mState = STATE_UNAVAILABLE;
256         private int mReason = REASON_IMS_SERVICE_DISCONNECTED;
257 
258         /*
259          * Remember the last return of verifyImsMmTelConfigured().
260          * true means ImsResolver found an IMS package for FEATURE_MMTEL.
261          *
262          * mReason is updated through connectionUnavailable triggered by ImsResolver.
263          * mHasConfig is update through notifyConfigChanged triggered by mReceiver.
264          * mHasConfig can be a redundancy of (mReason == REASON_NO_IMS_SERVICE_CONFIGURED).
265          * However, when a carrier config changes, we are not sure the order
266          * of execution of connectionUnavailable and notifyConfigChanged.
267          * So, it's safe to use a separated state to retain it.
268          * We assume mHasConfig is true, until it's determined explicitly.
269          */
270         private boolean mHasConfig = true;
271 
272         private int mSlotId = -1;
273         private String mLogPrefix = "";
274 
MmTelFeatureListener(int slotId)275         MmTelFeatureListener(int slotId) {
276             mSlotId = slotId;
277             mLogPrefix = "[" + slotId + ", MMTEL] ";
278             if (VDBG) logv(mLogPrefix + "created");
279 
280             mConnector = mMmTelFeatureFactory.create(
281                     mApp, slotId, TAG, this, new HandlerExecutor(mHandler));
282             mConnector.connect();
283         }
284 
setSubId(int subId)285         void setSubId(int subId) {
286             if (VDBG) logv(mLogPrefix + "setSubId mSubId=" + mSubId + ", subId=" + subId);
287             if (mSubId == subId) return;
288             logd(mLogPrefix + "setSubId changed subId=" + subId);
289 
290             // subId changed from valid to invalid
291             if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
292                 if (VDBG) logv(mLogPrefix + "setSubId remove ImsManager " + mSubId);
293                 // remove ImsManager reference associated with subId
294                 mSubIdToImsManagerCache.remove(mSubId);
295             }
296 
297             mSubId = subId;
298         }
299 
destroy()300         void destroy() {
301             if (VDBG) logv(mLogPrefix + "destroy");
302             mConnector.disconnect();
303             mConnector = null;
304         }
305 
306         @Override
connectionReady(ImsManager manager, int subId)307         public void connectionReady(ImsManager manager, int subId) {
308             logd(mLogPrefix + "connectionReady " + subId);
309 
310             mSubId = subId;
311             if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) return;
312 
313             // store ImsManager reference associated with subId
314             if (manager != null) {
315                 if (VDBG) logv(mLogPrefix + "connectionReady add ImsManager " + subId);
316                 mSubIdToImsManagerCache.put(subId, manager);
317             }
318 
319             mState = STATE_READY;
320             mReason = AVAILABLE;
321             mHasConfig = true;
322             onFeatureStateChange(mSubId, FEATURE_MMTEL, mState, mReason);
323         }
324 
325         @Override
connectionUnavailable(int reason)326         public void connectionUnavailable(int reason) {
327             logd(mLogPrefix + "connectionUnavailable reason=" + connectorReasonToString(reason));
328 
329             reason = convertReasonType(reason);
330             if (mReason == reason) return;
331 
332             // remove ImsManager reference associated with subId
333             if (VDBG) logv(mLogPrefix + "connectionUnavailable remove ImsManager " + mSubId);
334             mSubIdToImsManagerCache.remove(mSubId);
335 
336             connectionUnavailableInternal(reason);
337         }
338 
connectionUnavailableInternal(int reason)339         private void connectionUnavailableInternal(int reason) {
340             mState = STATE_UNAVAILABLE;
341             mReason = reason;
342 
343             /* If having no IMS package for MMTEL,
344              * discard the reason except REASON_NO_IMS_SERVICE_CONFIGURED. */
345             if (!mHasConfig && reason != REASON_NO_IMS_SERVICE_CONFIGURED) return;
346 
347             onFeatureStateChange(mSubId, FEATURE_MMTEL, mState, mReason);
348         }
349 
notifyConfigChanged(boolean hasConfig)350         void notifyConfigChanged(boolean hasConfig) {
351             if (mHasConfig == hasConfig) return;
352 
353             logd(mLogPrefix + "notifyConfigChanged " + hasConfig);
354 
355             mHasConfig = hasConfig;
356             if (hasConfig) {
357                 // REASON_NO_IMS_SERVICE_CONFIGURED is already reported to the clients,
358                 // since there is no configuration of IMS package for MMTEL.
359                 // Now, a carrier configuration change is notified and
360                 // the response from ImsResolver is changed from false to true.
361                 if (mState != STATE_READY) {
362                     if (mReason == REASON_NO_IMS_SERVICE_CONFIGURED) {
363                         // In this case, notify clients the reason, REASON_DISCONNCTED,
364                         // to update the state.
365                         connectionUnavailable(UNAVAILABLE_REASON_DISCONNECTED);
366                     } else {
367                         // ImsResolver and ImsStateCallbackController run with different Looper.
368                         // In this case, FeatureConnectorListener is updated ahead of this.
369                         // But, connectionUnavailable didn't notify clients since mHasConfig is
370                         // false. So, notify clients here.
371                         connectionUnavailableInternal(mReason);
372                     }
373                 }
374             } else {
375                 // FeatureConnector doesn't report UNAVAILABLE_REASON_IMS_UNSUPPORTED,
376                 // so report the reason here.
377                 connectionUnavailable(UNAVAILABLE_REASON_IMS_UNSUPPORTED);
378             }
379         }
380 
381         // called from onRegisterCallback
notifyState(CallbackWrapper wrapper)382         boolean notifyState(CallbackWrapper wrapper) {
383             if (VDBG) logv(mLogPrefix + "notifyState subId=" + wrapper.mSubId);
384 
385             return wrapper.notifyState(mSubId, FEATURE_MMTEL, mState, mReason);
386         }
387 
dump(IndentingPrintWriter pw)388         void dump(IndentingPrintWriter pw) {
389             pw.println("Listener={slotId=" + mSlotId
390                     + ", subId=" + mSubId
391                     + ", state=" + ImsFeature.STATE_LOG_MAP.get(mState)
392                     + ", reason=" + imsStateReasonToString(mReason)
393                     + ", hasConfig=" + mHasConfig
394                     + "}");
395         }
396     }
397 
398     private final class RcsFeatureListener implements FeatureConnector.Listener<RcsFeatureManager> {
399         private FeatureConnector<RcsFeatureManager> mConnector;
400         private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
401         private int mState = STATE_UNAVAILABLE;
402         private int mReason = REASON_IMS_SERVICE_DISCONNECTED;
403 
404         /*
405          * Remember the last return of verifyImsMmTelConfigured().
406          * true means ImsResolver found an IMS package for FEATURE_RCS.
407          *
408          * mReason is updated through connectionUnavailable triggered by ImsResolver.
409          * mHasConfig is update through notifyConfigChanged triggered by mReceiver,
410          * and notifyExternalRcsState which triggered by TelephonyRcsService refers it.
411          * mHasConfig can be a redundancy of (mReason == REASON_NO_IMS_SERVICE_CONFIGURED).
412          * However, when a carrier config changes, we are not sure the order
413          * of execution of connectionUnavailable, notifyConfigChanged and notifyExternalRcsState.
414          * So, it's safe to use a separated state to retain it.
415          * We assume mHasConfig is true, until it's determined explicitly.
416          */
417         private boolean mHasConfig = true;
418 
419         /*
420          * TelephonyRcsService doesn’t try to connect to RcsFeature if there is no active feature
421          * for a given subscription. The active features are declared by carrier configs and
422          * configuration resources. The APIs of ImsRcsManager and SipDelegateManager are available
423          * only when the RcsFeatureController has a STATE_READY state connection.
424          * This configuration is different from the configuration of IMS package for RCS.
425          * ImsStateCallbackController's FeatureConnectorListener can be STATE_READY state,
426          * even in case there is no active RCS feature. But Manager's APIs throws exception.
427          *
428          * For RCS, in addition to mHasConfig, the sate of TelephonyRcsService and
429          * RcsFeatureConnector will be traced to determine the state to be notified to clients.
430          */
431         private ExternalRcsFeatureState mExternalState = null;
432 
433         private int mSlotId = -1;
434         private String mLogPrefix = "";
435 
RcsFeatureListener(int slotId)436         RcsFeatureListener(int slotId) {
437             mSlotId = slotId;
438             mLogPrefix = "[" + slotId + ", RCS] ";
439             if (VDBG) logv(mLogPrefix + "created");
440 
441             mConnector = mRcsFeatureFactory.create(
442                     mApp, slotId, this, new HandlerExecutor(mHandler), TAG);
443             mConnector.connect();
444         }
445 
setSubId(int subId)446         void setSubId(int subId) {
447             if (VDBG) logv(mLogPrefix + "setSubId mSubId=" + mSubId + ", subId=" + subId);
448             if (mSubId == subId) return;
449             logd(mLogPrefix + "setSubId changed subId=" + subId);
450 
451             mSubId = subId;
452         }
453 
destroy()454         void destroy() {
455             if (VDBG) logv(mLogPrefix + "destroy");
456 
457             mConnector.disconnect();
458             mConnector = null;
459         }
460 
461         @Override
connectionReady(RcsFeatureManager manager, int subId)462         public void connectionReady(RcsFeatureManager manager, int subId) {
463             logd(mLogPrefix + "connectionReady " + subId);
464 
465             mSubId = subId;
466             if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) return;
467 
468             mState = STATE_READY;
469             mReason = AVAILABLE;
470             mHasConfig = true;
471 
472             if (mExternalState != null && mExternalState.isReady()) {
473                 onFeatureStateChange(mSubId, FEATURE_RCS, mState, mReason);
474             }
475         }
476 
477         @Override
connectionUnavailable(int reason)478         public void connectionUnavailable(int reason) {
479             logd(mLogPrefix + "connectionUnavailable reason=" + connectorReasonToString(reason));
480 
481             reason = convertReasonType(reason);
482             if (mReason == reason) return;
483 
484             connectionUnavailableInternal(reason);
485         }
486 
connectionUnavailableInternal(int reason)487         private void connectionUnavailableInternal(int reason) {
488             mState = STATE_UNAVAILABLE;
489             mReason = reason;
490 
491             /* If having no IMS package for RCS,
492              * dicard the reason except REASON_NO_IMS_SERVICE_CONFIGURED. */
493             if (!mHasConfig && reason != REASON_NO_IMS_SERVICE_CONFIGURED) return;
494 
495             if (mExternalState == null && reason != REASON_NO_IMS_SERVICE_CONFIGURED) {
496                 // Wait until TelephonyRcsService notifies its state.
497                 return;
498             }
499 
500             if (mExternalState != null && !mExternalState.hasActiveFeatures()) {
501                 // notifyExternalRcsState has notified REASON_NO_IMS_SERVICE_CONFIGURED already
502                 // ignore it
503                 return;
504             }
505 
506             if ((mExternalState != null && mExternalState.hasActiveFeatures())
507                     || mReason == REASON_NO_IMS_SERVICE_CONFIGURED) {
508                 onFeatureStateChange(mSubId, FEATURE_RCS, mState, mReason);
509             }
510         }
511 
notifyConfigChanged(boolean hasConfig)512         void notifyConfigChanged(boolean hasConfig) {
513             if (mHasConfig == hasConfig) return;
514 
515             logd(mLogPrefix + "notifyConfigChanged " + hasConfig);
516 
517             mHasConfig = hasConfig;
518             if (hasConfig) {
519                 // REASON_NO_IMS_SERVICE_CONFIGURED is already reported to the clients,
520                 // since there is no configuration of IMS package for RCS.
521                 // Now, a carrier configuration change is notified and
522                 // the response from ImsResolver is changed from false to true.
523                 if (mState != STATE_READY) {
524                     if (mReason == REASON_NO_IMS_SERVICE_CONFIGURED) {
525                         // In this case, notify clients the reason, REASON_DISCONNCTED,
526                         // to update the state.
527                         connectionUnavailable(UNAVAILABLE_REASON_DISCONNECTED);
528                     } else {
529                         // ImsResolver and ImsStateCallbackController run with different Looper.
530                         // In this case, FeatureConnectorListener is updated ahead of this.
531                         // But, connectionUnavailable didn't notify clients since mHasConfig is
532                         // false. So, notify clients here.
533                         connectionUnavailableInternal(mReason);
534                     }
535                 }
536             } else {
537                 // FeatureConnector doesn't report UNAVAILABLE_REASON_IMS_UNSUPPORTED,
538                 // so report the reason here.
539                 connectionUnavailable(UNAVAILABLE_REASON_IMS_UNSUPPORTED);
540             }
541         }
542 
notifyExternalRcsState(ExternalRcsFeatureState fs)543         void notifyExternalRcsState(ExternalRcsFeatureState fs) {
544             if (VDBG) {
545                 logv(mLogPrefix + "notifyExternalRcsState"
546                         + " state=" + (fs.mState == STATE_UNKNOWN
547                                 ? "" : ImsFeature.STATE_LOG_MAP.get(fs.mState))
548                         + ", reason=" + imsStateReasonToString(fs.mReason));
549             }
550 
551             ExternalRcsFeatureState oldFs = mExternalState;
552             // External state is from TelephonyRcsService while a feature is added or removed.
553             if (fs.mState == STATE_UNKNOWN) {
554                 if (oldFs != null) fs.mState = oldFs.mState;
555                 else fs.mState = STATE_UNAVAILABLE;
556             }
557 
558             mExternalState = fs;
559 
560             // No IMS package found.
561             // REASON_NO_IMS_SERVICE_CONFIGURED is notified to clients already.
562             if (!mHasConfig) return;
563 
564             if (fs.hasActiveFeatures()) {
565                 if (mState == STATE_READY) {
566                     if ((oldFs == null || !oldFs.isReady()) && fs.isReady()) {
567                         // it is waiting RcsFeatureConnector's notification.
568                         // notify clients here.
569                         onFeatureStateChange(mSubId, FEATURE_RCS, mState, mReason);
570                     } else if (!fs.isReady()) {
571                         // Wait RcsFeatureConnector's notification
572                     } else {
573                         // ignore duplicated notification
574                     }
575                 }
576             } else {
577                 // notify only once
578                 if (oldFs == null || oldFs.hasActiveFeatures()) {
579                     if (mReason != REASON_NO_IMS_SERVICE_CONFIGURED) {
580                         onFeatureStateChange(
581                                 mSubId, FEATURE_RCS, STATE_UNAVAILABLE,
582                                 REASON_NO_IMS_SERVICE_CONFIGURED);
583                     }
584                 } else {
585                     // ignore duplicated notification
586                 }
587             }
588         }
589 
590         // called from onRegisterCallback
notifyState(CallbackWrapper wrapper)591         boolean notifyState(CallbackWrapper wrapper) {
592             if (VDBG) logv(mLogPrefix + "notifyState subId=" + wrapper.mSubId);
593 
594             if (mHasConfig) {
595                 if (mExternalState == null) {
596                     // Wait until TelephonyRcsService notifies its state.
597                     return wrapper.notifyState(mSubId, FEATURE_RCS, STATE_UNAVAILABLE,
598                             REASON_IMS_SERVICE_DISCONNECTED);
599                 } else if (!mExternalState.hasActiveFeatures()) {
600                     return wrapper.notifyState(mSubId, FEATURE_RCS, STATE_UNAVAILABLE,
601                             REASON_NO_IMS_SERVICE_CONFIGURED);
602                 }
603             }
604 
605             return wrapper.notifyState(mSubId, FEATURE_RCS, mState, mReason);
606         }
607 
dump(IndentingPrintWriter pw)608         void dump(IndentingPrintWriter pw) {
609             pw.println("Listener={slotId=" + mSlotId
610                     + ", subId=" + mSubId
611                     + ", state=" + ImsFeature.STATE_LOG_MAP.get(mState)
612                     + ", reason=" + imsStateReasonToString(mReason)
613                     + ", hasConfig=" + mHasConfig
614                     + ", isReady=" + (mExternalState == null ? false : mExternalState.isReady())
615                     + ", hasFeatures=" + (mExternalState == null ? false
616                             : mExternalState.hasActiveFeatures())
617                     + "}");
618         }
619     }
620 
621     /**
622      * A wrapper class for the callback registered
623      */
624     private static class CallbackWrapper {
625         private final int mSubId;
626         private final int mRequiredFeature;
627         private final IImsStateCallback mCallback;
628         private final IBinder mBinder;
629         private final String mCallingPackage;
630         private int mLastReason = NOT_INITIALIZED;
631 
CallbackWrapper(int subId, int feature, IImsStateCallback callback, String callingPackage)632         CallbackWrapper(int subId, int feature, IImsStateCallback callback,
633                 String callingPackage) {
634             mSubId = subId;
635             mRequiredFeature = feature;
636             mCallback = callback;
637             mBinder = callback.asBinder();
638             mCallingPackage = callingPackage;
639         }
640 
641         /**
642          * @return false when accessing callback binder throws an Exception.
643          * That means the callback binder is not valid any longer.
644          * The death of remote process can cause this.
645          * This instance shall be removed from the list.
646          */
notifyState(int subId, int feature, int state, int reason)647         boolean notifyState(int subId, int feature, int state, int reason) {
648             if (VDBG) {
649                 logv("CallbackWrapper notifyState subId=" + subId
650                         + ", feature=" + ImsFeature.FEATURE_LOG_MAP.get(feature)
651                         + ", state=" + ImsFeature.STATE_LOG_MAP.get(state)
652                         + ", reason=" + imsStateReasonToString(reason));
653             }
654 
655             try {
656                 if (state == STATE_READY) {
657                     mCallback.onAvailable();
658                 } else {
659                     mCallback.onUnavailable(reason);
660                 }
661                 mLastReason = reason;
662             } catch (Exception e) {
663                 loge("CallbackWrapper notifyState e=" + e);
664                 return false;
665             }
666 
667             return true;
668         }
669 
notifyInactive()670         void notifyInactive() {
671             logd("CallbackWrapper notifyInactive subId=" + mSubId);
672 
673             try {
674                 mCallback.onUnavailable(REASON_SUBSCRIPTION_INACTIVE);
675             } catch (Exception e) {
676                 // ignored
677             }
678         }
679 
dump(IndentingPrintWriter pw)680         void dump(IndentingPrintWriter pw) {
681             pw.println("CallbackWrapper={subId=" + mSubId
682                     + ", feature=" + ImsFeature.FEATURE_LOG_MAP.get(mRequiredFeature)
683                     + ", reason=" + imsStateReasonToString(mLastReason)
684                     + ", pkg=" + mCallingPackage
685                     + "}");
686         }
687     }
688 
689     private static class ExternalRcsFeatureState {
690         private int mSlotId;
691         private int mState = STATE_UNAVAILABLE;
692         private int mReason = NOT_INITIALIZED;
693 
ExternalRcsFeatureState(int slotId, int state, int reason)694         ExternalRcsFeatureState(int slotId, int state, int reason) {
695             mSlotId = slotId;
696             mState = state;
697             mReason = reason;
698         }
699 
hasActiveFeatures()700         boolean hasActiveFeatures() {
701             return mReason != REASON_NO_IMS_SERVICE_CONFIGURED;
702         }
703 
isReady()704         boolean isReady() {
705             return mState == STATE_READY;
706         }
707     }
708 
709     /**
710      * create an instance
711      */
make(PhoneGlobals app, int numSlots)712     public static ImsStateCallbackController make(PhoneGlobals app, int numSlots) {
713         synchronized (ImsStateCallbackController.class) {
714             if (sInstance == null) {
715                 logd("ImsStateCallbackController created");
716 
717                 HandlerThread handlerThread = new HandlerThread(TAG);
718                 handlerThread.start();
719                 sInstance = new ImsStateCallbackController(app, handlerThread.getLooper(), numSlots,
720                         ImsManager::getConnector, RcsFeatureManager::getConnector,
721                         ImsResolver.getInstance());
722             }
723         }
724         return sInstance;
725     }
726 
727     @VisibleForTesting
ImsStateCallbackController(PhoneGlobals app, Looper looper, int numSlots, MmTelFeatureConnectorFactory mmTelFactory, RcsFeatureConnectorFactory rcsFactory, ImsResolver imsResolver)728     public ImsStateCallbackController(PhoneGlobals app, Looper looper, int numSlots,
729             MmTelFeatureConnectorFactory mmTelFactory, RcsFeatureConnectorFactory rcsFactory,
730             ImsResolver imsResolver) {
731         mApp = app;
732         mHandler = new MyHandler(looper);
733         mImsResolver = imsResolver;
734         mSubscriptionManager = mApp.getSystemService(SubscriptionManager.class);
735         mTelephonyRegistryManager = mApp.getSystemService(TelephonyRegistryManager.class);
736         mMmTelFeatureFactory = mmTelFactory;
737         mRcsFeatureFactory = rcsFactory;
738 
739         updateFeatureControllerSize(numSlots);
740 
741         mTelephonyRegistryManager.addOnSubscriptionsChangedListener(
742                 mSubChangedListener, mHandler::post);
743 
744         PhoneConfigurationManager.registerForMultiSimConfigChange(mHandler,
745                 EVENT_MSIM_CONFIGURATION_CHANGE, null);
746 
747         mApp.registerReceiver(mReceiver, new IntentFilter(
748                 CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
749 
750         onSubChanged();
751     }
752 
753     /**
754      * Update the number of {@link RcsFeatureController}s that are created based on the number of
755      * active slots on the device.
756      */
757     @VisibleForTesting
updateFeatureControllerSize(int newNumSlots)758     public void updateFeatureControllerSize(int newNumSlots) {
759         if (mNumSlots != newNumSlots) {
760             logd("updateFeatures: oldSlots=" + mNumSlots
761                     + ", newNumSlots=" + newNumSlots);
762             if (mNumSlots < newNumSlots) {
763                 for (int i = mNumSlots; i < newNumSlots; i++) {
764                     MmTelFeatureListener m = new MmTelFeatureListener(i);
765                     mMmTelFeatureListeners.put(i, m);
766                     RcsFeatureListener r = new RcsFeatureListener(i);
767                     mRcsFeatureListeners.put(i, r);
768                 }
769             } else {
770                 for (int i = (mNumSlots - 1); i > (newNumSlots - 1); i--) {
771                     MmTelFeatureListener m = mMmTelFeatureListeners.get(i);
772                     if (m != null) {
773                         mMmTelFeatureListeners.remove(i);
774                         m.destroy();
775                     }
776                     RcsFeatureListener r = mRcsFeatureListeners.get(i);
777                     if (r != null) {
778                         mRcsFeatureListeners.remove(i);
779                         r.destroy();
780                     }
781                 }
782             }
783         }
784         mNumSlots = newNumSlots;
785     }
786 
787     /**
788      * Dependencies for testing.
789      */
790     @VisibleForTesting
onSubChanged()791     public void onSubChanged() {
792         for (int i = 0; i < mMmTelFeatureListeners.size(); i++) {
793             MmTelFeatureListener l = mMmTelFeatureListeners.valueAt(i);
794             l.setSubId(getSubId(i));
795         }
796 
797         for (int i = 0; i < mRcsFeatureListeners.size(); i++) {
798             RcsFeatureListener l = mRcsFeatureListeners.valueAt(i);
799             l.setSubId(getSubId(i));
800         }
801 
802         if (mWrappers.size() == 0) return;
803 
804         ArrayList<IBinder> inactiveCallbacks = new ArrayList<>();
805         final int[] activeSubs = mSubscriptionManager.getActiveSubscriptionIdList();
806 
807         if (VDBG) logv("onSubChanged activeSubs=" + Arrays.toString(activeSubs));
808 
809         // Remove callbacks for inactive subscriptions
810         for (IBinder binder : mWrappers.keySet()) {
811             CallbackWrapper wrapper = mWrappers.get(binder);
812             if (wrapper != null) {
813                 if (!isActive(activeSubs, wrapper.mSubId)) {
814                     // inactive subscription
815                     inactiveCallbacks.add(binder);
816                 }
817             } else {
818                 // unexpected, remove it
819                 inactiveCallbacks.add(binder);
820             }
821         }
822         removeInactiveCallbacks(inactiveCallbacks, "onSubChanged");
823     }
824 
onFeatureStateChange(int subId, int feature, int state, int reason)825     private void onFeatureStateChange(int subId, int feature, int state, int reason) {
826         if (VDBG) {
827             logv("onFeatureStateChange subId=" + subId
828                     + ", feature=" + ImsFeature.FEATURE_LOG_MAP.get(feature)
829                     + ", state=" + ImsFeature.STATE_LOG_MAP.get(state)
830                     + ", reason=" + imsStateReasonToString(reason));
831         }
832 
833         ArrayList<IBinder> inactiveCallbacks = new ArrayList<>();
834         mWrappers.values().forEach(wrapper -> {
835             if (subId == wrapper.mSubId
836                     && feature == wrapper.mRequiredFeature
837                     && !wrapper.notifyState(subId, feature, state, reason)) {
838                 // callback has exception, remove it
839                 inactiveCallbacks.add(wrapper.mBinder);
840             }
841         });
842         removeInactiveCallbacks(inactiveCallbacks, "onFeatureStateChange");
843     }
844 
onRegisterCallback(CallbackWrapper wrapper)845     private void onRegisterCallback(CallbackWrapper wrapper) {
846         if (wrapper == null) return;
847 
848         if (VDBG) logv("onRegisterCallback before size=" + mWrappers.size());
849         if (VDBG) {
850             logv("onRegisterCallback subId=" + wrapper.mSubId
851                     + ", feature=" + wrapper.mRequiredFeature);
852         }
853 
854         // Not sure the following case can happen or not:
855         // step1) Subscription changed
856         // step2) ImsStateCallbackController not processed onSubChanged yet
857         // step3) Client registers with a strange subId
858         // The validity of the subId is checked PhoneInterfaceManager#registerImsStateCallback.
859         // So, register the wrapper here before trying to notifyState.
860         // TODO: implement the recovery for this case, notifying the current reson, in onSubChanged
861         mWrappers.put(wrapper.mBinder, wrapper);
862 
863         if (wrapper.mRequiredFeature == FEATURE_MMTEL) {
864             for (int i = 0; i < mMmTelFeatureListeners.size(); i++) {
865                 if (wrapper.mSubId == getSubId(i)) {
866                     MmTelFeatureListener l = mMmTelFeatureListeners.valueAt(i);
867                     if (!l.notifyState(wrapper)) {
868                         mWrappers.remove(wrapper.mBinder);
869                     }
870                     break;
871                 }
872             }
873         } else if (wrapper.mRequiredFeature == FEATURE_RCS) {
874             for (int i = 0; i < mRcsFeatureListeners.size(); i++) {
875                 if (wrapper.mSubId == getSubId(i)) {
876                     RcsFeatureListener l = mRcsFeatureListeners.valueAt(i);
877                     if (!l.notifyState(wrapper)) {
878                         mWrappers.remove(wrapper.mBinder);
879                     }
880                     break;
881                 }
882             }
883         }
884 
885         if (VDBG) logv("onRegisterCallback after size=" + mWrappers.size());
886     }
887 
onUnregisterCallback(IImsStateCallback cb)888     private void onUnregisterCallback(IImsStateCallback cb) {
889         if (cb == null) return;
890         mWrappers.remove(cb.asBinder());
891     }
892 
onCarrierConfigChanged(int slotId)893     private void onCarrierConfigChanged(int slotId) {
894         if (slotId >= mNumSlots) {
895             logd("onCarrierConfigChanged invalid slotId "
896                     + slotId + ", mNumSlots=" + mNumSlots);
897             return;
898         }
899 
900         logv("onCarrierConfigChanged slotId=" + slotId);
901 
902         boolean hasConfig = verifyImsMmTelConfigured(slotId);
903         if (slotId < mMmTelFeatureListeners.size()) {
904             MmTelFeatureListener listener = mMmTelFeatureListeners.valueAt(slotId);
905             listener.notifyConfigChanged(hasConfig);
906         }
907 
908         hasConfig = verifyImsRcsConfigured(slotId);
909         if (slotId < mRcsFeatureListeners.size()) {
910             RcsFeatureListener listener = mRcsFeatureListeners.valueAt(slotId);
911             listener.notifyConfigChanged(hasConfig);
912         }
913     }
914 
onExternalRcsStateChanged(ExternalRcsFeatureState fs)915     private void onExternalRcsStateChanged(ExternalRcsFeatureState fs) {
916         logv("onExternalRcsStateChanged slotId=" + fs.mSlotId
917                 + ", state=" + (fs.mState == STATE_UNKNOWN
918                         ? "" : ImsFeature.STATE_LOG_MAP.get(fs.mState))
919                 + ", reason=" + imsStateReasonToString(fs.mReason));
920 
921         RcsFeatureListener listener = mRcsFeatureListeners.get(fs.mSlotId);
922         if (listener != null) {
923             listener.notifyExternalRcsState(fs);
924         } else {
925             // unexpected state
926             loge("onExternalRcsStateChanged slotId=" + fs.mSlotId + ", no listener.");
927         }
928     }
929 
930     /**
931      * Interface to be notified from TelephonyRcsSerice and RcsFeatureController
932      *
933      * @param ready true if feature's state is STATE_READY. Valid only when it is true.
934      * @param hasActiveFeatures true if the RcsFeatureController has active features.
935      */
notifyExternalRcsStateChanged( int slotId, boolean ready, boolean hasActiveFeatures)936     public void notifyExternalRcsStateChanged(
937             int slotId, boolean ready, boolean hasActiveFeatures) {
938         int state = STATE_UNKNOWN;
939         int reason = REASON_IMS_SERVICE_DISCONNECTED;
940 
941         if (ready) {
942             // From RcsFeatureController
943             state = STATE_READY;
944             reason = AVAILABLE;
945         } else if (!hasActiveFeatures) {
946             // From TelephonyRcsService
947             reason = REASON_NO_IMS_SERVICE_CONFIGURED;
948             state = STATE_UNAVAILABLE;
949         } else {
950             // From TelephonyRcsService
951             // TelephonyRcsService doesn't know the exact state of FeatureConnection.
952             // Only when there is no feature, we can assume the state.
953         }
954 
955         if (VDBG) {
956             logv("notifyExternalRcsStateChanged slotId=" + slotId
957                     + ", ready=" + ready
958                     + ", hasActiveFeatures=" + hasActiveFeatures);
959         }
960 
961         ExternalRcsFeatureState fs = new ExternalRcsFeatureState(slotId, state, reason);
962         mHandler.sendMessage(mHandler.obtainMessage(EVENT_EXTERNAL_RCS_STATE_CHANGED, fs));
963     }
964 
965     /**
966      * Notifies carrier configuration has changed.
967      */
968     @VisibleForTesting
notifyCarrierConfigChanged(int slotId)969     public void notifyCarrierConfigChanged(int slotId) {
970         if (VDBG) logv("notifyCarrierConfigChanged slotId=" + slotId);
971         mHandler.sendMessage(mHandler.obtainMessage(EVENT_CARRIER_CONFIG_CHANGED, slotId, 0));
972     }
973     /**
974      * Register IImsStateCallback
975      *
976      * @param feature for which state is changed, ImsFeature.FEATURE_*
977      */
registerImsStateCallback(int subId, int feature, IImsStateCallback cb, String callingPackage)978     public void registerImsStateCallback(int subId, int feature, IImsStateCallback cb,
979             String callingPackage) {
980         if (VDBG) {
981             logv("registerImsStateCallback subId=" + subId
982                     + ", feature=" + feature + ", pkg=" + callingPackage);
983         }
984 
985         CallbackWrapper wrapper = new CallbackWrapper(subId, feature, cb, callingPackage);
986         mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_CALLBACK, wrapper));
987     }
988 
989     /**
990      * Unegister previously registered callback
991      */
unregisterImsStateCallback(IImsStateCallback cb)992     public void unregisterImsStateCallback(IImsStateCallback cb) {
993         if (VDBG) logv("unregisterImsStateCallback");
994 
995         mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_CALLBACK, cb));
996     }
997 
998     /**
999      * Get ImsManager reference associated with subId
1000      *
1001      * @param subId subscribe ID
1002      * @return instance of ImsManager associated with subId, but if ImsService is not
1003      * available return null
1004      */
getImsManager(int subId)1005     public ImsManager getImsManager(int subId) {
1006         if (VDBG) logv("getImsManager subId = " + subId);
1007 
1008         return mSubIdToImsManagerCache.get(subId);
1009     }
1010 
removeInactiveCallbacks( ArrayList<IBinder> inactiveCallbacks, String message)1011     private void removeInactiveCallbacks(
1012             ArrayList<IBinder> inactiveCallbacks, String message) {
1013         if (inactiveCallbacks == null || inactiveCallbacks.size() == 0) return;
1014 
1015         if (VDBG) {
1016             logv("removeInactiveCallbacks size="
1017                     + inactiveCallbacks.size() + " from " + message);
1018         }
1019 
1020         for (IBinder binder : inactiveCallbacks) {
1021             CallbackWrapper wrapper = mWrappers.get(binder);
1022             if (wrapper != null) {
1023                 // Send the reason REASON_SUBSCRIPTION_INACTIVE to the client
1024                 wrapper.notifyInactive();
1025                 mWrappers.remove(binder);
1026             }
1027         }
1028         inactiveCallbacks.clear();
1029     }
1030 
getSubId(int slotId)1031     private int getSubId(int slotId) {
1032         Phone phone = mPhoneFactoryProxy.getPhone(slotId);
1033         if (phone != null) return phone.getSubId();
1034         return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
1035     }
1036 
isActive(final int[] activeSubs, int subId)1037     private static boolean isActive(final int[] activeSubs, int subId) {
1038         for (int i : activeSubs) {
1039             if (i == subId) return true;
1040         }
1041         return false;
1042     }
1043 
convertReasonType(int reason)1044     private static int convertReasonType(int reason) {
1045         switch(reason) {
1046             case UNAVAILABLE_REASON_NOT_READY:
1047                 return REASON_IMS_SERVICE_NOT_READY;
1048             case UNAVAILABLE_REASON_IMS_UNSUPPORTED:
1049                 return REASON_NO_IMS_SERVICE_CONFIGURED;
1050             default:
1051                 break;
1052         }
1053 
1054         return REASON_IMS_SERVICE_DISCONNECTED;
1055     }
1056 
verifyImsMmTelConfigured(int slotId)1057     private boolean verifyImsMmTelConfigured(int slotId) {
1058         boolean ret = false;
1059         if (mImsResolver == null) {
1060             loge("verifyImsMmTelConfigured mImsResolver is null");
1061         } else {
1062             ret = mImsResolver.isImsServiceConfiguredForFeature(slotId, FEATURE_MMTEL);
1063         }
1064         if (VDBG) logv("verifyImsMmTelConfigured slotId=" + slotId + ", ret=" + ret);
1065         return ret;
1066     }
1067 
verifyImsRcsConfigured(int slotId)1068     private boolean verifyImsRcsConfigured(int slotId) {
1069         boolean ret = false;
1070         if (mImsResolver == null) {
1071             loge("verifyImsRcsConfigured mImsResolver is null");
1072         } else {
1073             ret = mImsResolver.isImsServiceConfiguredForFeature(slotId, FEATURE_RCS);
1074         }
1075         if (VDBG) logv("verifyImsRcsConfigured slotId=" + slotId + ", ret=" + ret);
1076         return ret;
1077     }
1078 
connectorReasonToString(int reason)1079     private static String connectorReasonToString(int reason) {
1080         switch(reason) {
1081             case UNAVAILABLE_REASON_DISCONNECTED:
1082                 return "DISCONNECTED";
1083             case UNAVAILABLE_REASON_NOT_READY:
1084                 return "NOT_READY";
1085             case UNAVAILABLE_REASON_IMS_UNSUPPORTED:
1086                 return "IMS_UNSUPPORTED";
1087             case UNAVAILABLE_REASON_SERVER_UNAVAILABLE:
1088                 return "SERVER_UNAVAILABLE";
1089             default:
1090                 break;
1091         }
1092         return "";
1093     }
1094 
imsStateReasonToString(int reason)1095     private static String imsStateReasonToString(int reason) {
1096         switch(reason) {
1097             case AVAILABLE:
1098                 return "READY";
1099             case REASON_UNKNOWN_TEMPORARY_ERROR:
1100                 return "UNKNOWN_TEMPORARY_ERROR";
1101             case REASON_UNKNOWN_PERMANENT_ERROR:
1102                 return "UNKNOWN_PERMANENT_ERROR";
1103             case REASON_IMS_SERVICE_DISCONNECTED:
1104                 return "IMS_SERVICE_DISCONNECTED";
1105             case REASON_NO_IMS_SERVICE_CONFIGURED:
1106                 return "NO_IMS_SERVICE_CONFIGURED";
1107             case REASON_SUBSCRIPTION_INACTIVE:
1108                 return "SUBSCRIPTION_INACTIVE";
1109             case REASON_IMS_SERVICE_NOT_READY:
1110                 return "IMS_SERVICE_NOT_READY";
1111             default:
1112                 break;
1113         }
1114         return "";
1115     }
1116 
1117     /**
1118      * PhoneFactory Dependencies for testing.
1119      */
1120     @VisibleForTesting
1121     public interface PhoneFactoryProxy {
1122         /**
1123          * Override getPhone for testing.
1124          */
getPhone(int index)1125         Phone getPhone(int index);
1126     }
1127 
1128     private PhoneFactoryProxy mPhoneFactoryProxy = new PhoneFactoryProxy() {
1129         @Override
1130         public Phone getPhone(int index) {
1131             return PhoneFactory.getPhone(index);
1132         }
1133     };
1134 
release()1135     private void release() {
1136         if (VDBG) logv("release");
1137 
1138         mTelephonyRegistryManager.removeOnSubscriptionsChangedListener(mSubChangedListener);
1139         mApp.unregisterReceiver(mReceiver);
1140 
1141         for (int i = 0; i < mMmTelFeatureListeners.size(); i++) {
1142             mMmTelFeatureListeners.valueAt(i).destroy();
1143         }
1144         mMmTelFeatureListeners.clear();
1145 
1146         for (int i = 0; i < mRcsFeatureListeners.size(); i++) {
1147             mRcsFeatureListeners.valueAt(i).destroy();
1148         }
1149         mRcsFeatureListeners.clear();
1150     }
1151 
1152     /**
1153      * destroy the instance
1154      */
1155     @VisibleForTesting
destroy()1156     public void destroy() {
1157         if (VDBG) logv("destroy it");
1158 
1159         release();
1160         mHandler.getLooper().quit();
1161     }
1162 
1163     /**
1164      * get the handler
1165      */
1166     @VisibleForTesting
getHandler()1167     public Handler getHandler() {
1168         return mHandler;
1169     }
1170 
1171     /**
1172      * Determine whether the callback is registered or not
1173      */
1174     @VisibleForTesting
isRegistered(IImsStateCallback cb)1175     public boolean isRegistered(IImsStateCallback cb) {
1176         if (cb == null) return false;
1177         return mWrappers.containsKey(cb.asBinder());
1178     }
1179 
1180     /**
1181      * Dump this instance into a readable format for dumpsys usage.
1182      */
dump(IndentingPrintWriter pw)1183     public void dump(IndentingPrintWriter pw) {
1184         pw.increaseIndent();
1185         synchronized (mDumpLock) {
1186             pw.println("CallbackWrappers:");
1187             pw.increaseIndent();
1188             mWrappers.values().forEach(wrapper -> wrapper.dump(pw));
1189             pw.decreaseIndent();
1190             pw.println("MmTelFeatureListeners:");
1191             pw.increaseIndent();
1192             for (int i = 0; i < mNumSlots; i++) {
1193                 MmTelFeatureListener l = mMmTelFeatureListeners.get(i);
1194                 if (l == null) continue;
1195                 l.dump(pw);
1196             }
1197             pw.decreaseIndent();
1198             pw.println("RcsFeatureListeners:");
1199             pw.increaseIndent();
1200             for (int i = 0; i < mNumSlots; i++) {
1201                 RcsFeatureListener l = mRcsFeatureListeners.get(i);
1202                 if (l == null) continue;
1203                 l.dump(pw);
1204             }
1205             pw.decreaseIndent();
1206             pw.println("Most recent logs:");
1207             pw.increaseIndent();
1208             sLocalLog.dump(pw);
1209             pw.decreaseIndent();
1210         }
1211         pw.decreaseIndent();
1212     }
1213 
logv(String msg)1214     private static void logv(String msg) {
1215         Rlog.d(TAG, msg);
1216     }
1217 
logd(String msg)1218     private static void logd(String msg) {
1219         Rlog.d(TAG, msg);
1220         sLocalLog.log(msg);
1221     }
1222 
loge(String msg)1223     private static void loge(String msg) {
1224         Rlog.e(TAG, msg);
1225         sLocalLog.log(msg);
1226     }
1227 }
1228