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