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