blob: 019c1cab5c91c5ea5d0331a9167165af906ee339 [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;
62import com.android.internal.telephony.ims.ImsResolver;
63import com.android.internal.telephony.util.HandlerExecutor;
64import com.android.internal.util.IndentingPrintWriter;
65import com.android.services.telephony.rcs.RcsFeatureController;
66import com.android.telephony.Rlog;
67
68import java.util.ArrayList;
69import java.util.Arrays;
70import java.util.HashMap;
joonhunshin49f0aed2022-08-05 08:33:05 +000071import java.util.concurrent.ConcurrentHashMap;
Hunsuk Choic7ebc0f2021-11-15 23:46:41 +000072import java.util.concurrent.Executor;
73
74/**
75 * Implementation of the controller managing {@link ImsStateCallback}s
76 */
77public 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 */
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 */
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 */
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
joonhunshin49f0aed2022-08-05 08:33:05 +0000147 // Container to store ImsManager instance by subId
148 private final ConcurrentHashMap<Integer, ImsManager> mSubIdToImsManagerCache =
149 new ConcurrentHashMap<>();
150
Hunsuk Choic7ebc0f2021-11-15 23:46:41 +0000151 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 {
205 MyHandler(Looper looper) {
206 super(looper);
207 }
208
209 @Override
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
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
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
joonhunshin49f0aed2022-08-05 08:33:05 +0000290 // 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
Hunsuk Choic7ebc0f2021-11-15 23:46:41 +0000297 mSubId = subId;
298 }
299
300 void destroy() {
301 if (VDBG) logv(mLogPrefix + "destroy");
302 mConnector.disconnect();
303 mConnector = null;
304 }
305
306 @Override
Jonggeon Kim0da9a7b2021-12-23 08:56:50 +0000307 public void connectionReady(ImsManager manager, int subId) {
Hunsuk Choif34a6b22022-01-11 03:41:34 +0000308 logd(mLogPrefix + "connectionReady " + subId);
Hunsuk Choic7ebc0f2021-11-15 23:46:41 +0000309
Hunsuk Choif34a6b22022-01-11 03:41:34 +0000310 mSubId = subId;
Hunsuk Choibd34bdf2022-03-31 03:23:04 +0000311 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) return;
312
joonhunshin49f0aed2022-08-05 08:33:05 +0000313 // 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
Hunsuk Choic7ebc0f2021-11-15 23:46:41 +0000319 mState = STATE_READY;
320 mReason = AVAILABLE;
321 mHasConfig = true;
322 onFeatureStateChange(mSubId, FEATURE_MMTEL, mState, mReason);
323 }
324
325 @Override
326 public void connectionUnavailable(int reason) {
327 logd(mLogPrefix + "connectionUnavailable reason=" + connectorReasonToString(reason));
328
329 reason = convertReasonType(reason);
330 if (mReason == reason) return;
331
joonhunshin49f0aed2022-08-05 08:33:05 +0000332 // remove ImsManager reference associated with subId
333 if (VDBG) logv(mLogPrefix + "connectionUnavailable remove ImsManager " + mSubId);
334 mSubIdToImsManagerCache.remove(mSubId);
335
Hunsuk Choic7ebc0f2021-11-15 23:46:41 +0000336 connectionUnavailableInternal(reason);
337 }
338
339 private void connectionUnavailableInternal(int reason) {
340 mState = STATE_UNAVAILABLE;
341 mReason = reason;
342
343 /* If having no IMS package for MMTEL,
joonhunshin49f0aed2022-08-05 08:33:05 +0000344 * discard the reason except REASON_NO_IMS_SERVICE_CONFIGURED. */
Hunsuk Choic7ebc0f2021-11-15 23:46:41 +0000345 if (!mHasConfig && reason != REASON_NO_IMS_SERVICE_CONFIGURED) return;
346
347 onFeatureStateChange(mSubId, FEATURE_MMTEL, mState, mReason);
348 }
349
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
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
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
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
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
454 void destroy() {
455 if (VDBG) logv(mLogPrefix + "destroy");
456
457 mConnector.disconnect();
458 mConnector = null;
459 }
460
461 @Override
Jonggeon Kim0da9a7b2021-12-23 08:56:50 +0000462 public void connectionReady(RcsFeatureManager manager, int subId) {
Hunsuk Choif34a6b22022-01-11 03:41:34 +0000463 logd(mLogPrefix + "connectionReady " + subId);
Hunsuk Choic7ebc0f2021-11-15 23:46:41 +0000464
Hunsuk Choif34a6b22022-01-11 03:41:34 +0000465 mSubId = subId;
Hunsuk Choibd34bdf2022-03-31 03:23:04 +0000466 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) return;
467
Hunsuk Choic7ebc0f2021-11-15 23:46:41 +0000468 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
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
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
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
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
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
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
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 */
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
670 void notifyInactive() {
Hunsuk Choibd34bdf2022-03-31 03:23:04 +0000671 logd("CallbackWrapper notifyInactive subId=" + mSubId);
Hunsuk Choic7ebc0f2021-11-15 23:46:41 +0000672
673 try {
674 mCallback.onUnavailable(REASON_SUBSCRIPTION_INACTIVE);
675 } catch (Exception e) {
676 // ignored
677 }
678 }
679
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
694 ExternalRcsFeatureState(int slotId, int state, int reason) {
695 mSlotId = slotId;
696 mState = state;
697 mReason = reason;
698 }
699
700 boolean hasActiveFeatures() {
701 return mReason != REASON_NO_IMS_SERVICE_CONFIGURED;
702 }
703
704 boolean isReady() {
705 return mState == STATE_READY;
706 }
707 }
708
709 /**
710 * create an instance
711 */
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
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(
Nathan Harold4d544b62023-06-21 13:48:40 -0700742 mSubChangedListener, mHandler::post);
Hunsuk Choic7ebc0f2021-11-15 23:46:41 +0000743
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
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
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
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
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++) {
Hunsuk Choif34a6b22022-01-11 03:41:34 +0000865 if (wrapper.mSubId == getSubId(i)) {
866 MmTelFeatureListener l = mMmTelFeatureListeners.valueAt(i);
867 if (!l.notifyState(wrapper)) {
868 mWrappers.remove(wrapper.mBinder);
869 }
Hunsuk Choic7ebc0f2021-11-15 23:46:41 +0000870 break;
871 }
872 }
873 } else if (wrapper.mRequiredFeature == FEATURE_RCS) {
874 for (int i = 0; i < mRcsFeatureListeners.size(); i++) {
Hunsuk Choif34a6b22022-01-11 03:41:34 +0000875 if (wrapper.mSubId == getSubId(i)) {
876 RcsFeatureListener l = mRcsFeatureListeners.valueAt(i);
877 if (!l.notifyState(wrapper)) {
878 mWrappers.remove(wrapper.mBinder);
879 }
Hunsuk Choic7ebc0f2021-11-15 23:46:41 +0000880 break;
881 }
882 }
883 }
884
885 if (VDBG) logv("onRegisterCallback after size=" + mWrappers.size());
886 }
887
888 private void onUnregisterCallback(IImsStateCallback cb) {
889 if (cb == null) return;
890 mWrappers.remove(cb.asBinder());
891 }
892
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
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 */
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
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 */
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 */
992 public void unregisterImsStateCallback(IImsStateCallback cb) {
993 if (VDBG) logv("unregisterImsStateCallback");
994
995 mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_CALLBACK, cb));
996 }
997
joonhunshin49f0aed2022-08-05 08:33:05 +0000998 /**
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 */
1005 public ImsManager getImsManager(int subId) {
1006 if (VDBG) logv("getImsManager subId = " + subId);
1007
1008 return mSubIdToImsManagerCache.get(subId);
1009 }
1010
Hunsuk Choic7ebc0f2021-11-15 23:46:41 +00001011 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
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
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
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
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
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
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
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 */
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
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
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
1167 public Handler getHandler() {
1168 return mHandler;
1169 }
1170
1171 /**
1172 * Determine whether the callback is registered or not
1173 */
1174 @VisibleForTesting
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 */
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
1214 private static void logv(String msg) {
1215 Rlog.d(TAG, msg);
1216 }
1217
1218 private static void logd(String msg) {
1219 Rlog.d(TAG, msg);
1220 sLocalLog.log(msg);
1221 }
1222
1223 private static void loge(String msg) {
1224 Rlog.e(TAG, msg);
1225 sLocalLog.log(msg);
1226 }
1227}