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