blob: 28fca598c57ecfd0cf7b49242236ccdc3daa7bf4 [file] [log] [blame]
Hunsuk Choic7ebc0f2021-11-15 23:46:41 +00001/*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.phone;
18
19import static android.telephony.ims.ImsStateCallback.REASON_IMS_SERVICE_DISCONNECTED;
20import static android.telephony.ims.ImsStateCallback.REASON_IMS_SERVICE_NOT_READY;
21import static android.telephony.ims.ImsStateCallback.REASON_NO_IMS_SERVICE_CONFIGURED;
22import static android.telephony.ims.ImsStateCallback.REASON_SUBSCRIPTION_INACTIVE;
23import static android.telephony.ims.ImsStateCallback.REASON_UNKNOWN_PERMANENT_ERROR;
24import static android.telephony.ims.ImsStateCallback.REASON_UNKNOWN_TEMPORARY_ERROR;
25import static android.telephony.ims.feature.ImsFeature.FEATURE_MMTEL;
26import static android.telephony.ims.feature.ImsFeature.FEATURE_RCS;
27import static android.telephony.ims.feature.ImsFeature.STATE_READY;
28import static android.telephony.ims.feature.ImsFeature.STATE_UNAVAILABLE;
29
30import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED;
31import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_IMS_UNSUPPORTED;
32import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_NOT_READY;
33import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE;
34
35import android.content.BroadcastReceiver;
36import android.content.Context;
37import android.content.Intent;
38import android.content.IntentFilter;
39import android.os.AsyncResult;
40import android.os.Bundle;
41import android.os.Handler;
42import android.os.HandlerThread;
43import android.os.IBinder;
44import android.os.Looper;
45import android.os.Message;
46import android.telephony.CarrierConfigManager;
47import android.telephony.SubscriptionManager;
48import android.telephony.TelephonyRegistryManager;
49import android.telephony.ims.feature.ImsFeature;
50import android.util.LocalLog;
51import android.util.Log;
52import android.util.SparseArray;
53
54import com.android.ims.FeatureConnector;
55import com.android.ims.ImsManager;
56import com.android.ims.RcsFeatureManager;
57import com.android.internal.annotations.VisibleForTesting;
58import com.android.internal.telephony.IImsStateCallback;
59import com.android.internal.telephony.Phone;
60import com.android.internal.telephony.PhoneConfigurationManager;
61import com.android.internal.telephony.PhoneFactory;
62import com.android.internal.telephony.ims.ImsResolver;
63import com.android.internal.telephony.util.HandlerExecutor;
64import com.android.internal.util.IndentingPrintWriter;
65import com.android.services.telephony.rcs.RcsFeatureController;
66import com.android.telephony.Rlog;
67
68import java.util.ArrayList;
69import java.util.Arrays;
70import java.util.HashMap;
71import java.util.concurrent.Executor;
72
73/**
74 * Implementation of the controller managing {@link ImsStateCallback}s
75 */
76public 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 */
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 */
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 */
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 {
200 MyHandler(Looper looper) {
201 super(looper);
202 }
203
204 @Override
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
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
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
288 void destroy() {
289 if (VDBG) logv(mLogPrefix + "destroy");
290 mConnector.disconnect();
291 mConnector = null;
292 }
293
294 @Override
295 public void connectionReady(ImsManager manager) {
296 logd(mLogPrefix + "connectionReady");
297
298 mState = STATE_READY;
299 mReason = AVAILABLE;
300 mHasConfig = true;
301 onFeatureStateChange(mSubId, FEATURE_MMTEL, mState, mReason);
302 }
303
304 @Override
305 public void connectionUnavailable(int reason) {
306 logd(mLogPrefix + "connectionUnavailable reason=" + connectorReasonToString(reason));
307
308 reason = convertReasonType(reason);
309 if (mReason == reason) return;
310
311 connectionUnavailableInternal(reason);
312 }
313
314 private void connectionUnavailableInternal(int reason) {
315 mState = STATE_UNAVAILABLE;
316 mReason = reason;
317
318 /* If having no IMS package for MMTEL,
319 * dicard the reason except REASON_NO_IMS_SERVICE_CONFIGURED. */
320 if (!mHasConfig && reason != REASON_NO_IMS_SERVICE_CONFIGURED) return;
321
322 onFeatureStateChange(mSubId, FEATURE_MMTEL, mState, mReason);
323 }
324
325 void notifyConfigChanged(boolean hasConfig) {
326 if (mHasConfig == hasConfig) return;
327
328 logd(mLogPrefix + "notifyConfigChanged " + hasConfig);
329
330 mHasConfig = hasConfig;
331 if (hasConfig) {
332 // REASON_NO_IMS_SERVICE_CONFIGURED is already reported to the clients,
333 // since there is no configuration of IMS package for MMTEL.
334 // Now, a carrier configuration change is notified and
335 // the response from ImsResolver is changed from false to true.
336 if (mState != STATE_READY) {
337 if (mReason == REASON_NO_IMS_SERVICE_CONFIGURED) {
338 // In this case, notify clients the reason, REASON_DISCONNCTED,
339 // to update the state.
340 connectionUnavailable(UNAVAILABLE_REASON_DISCONNECTED);
341 } else {
342 // ImsResolver and ImsStateCallbackController run with different Looper.
343 // In this case, FeatureConnectorListener is updated ahead of this.
344 // But, connectionUnavailable didn't notify clients since mHasConfig is
345 // false. So, notify clients here.
346 connectionUnavailableInternal(mReason);
347 }
348 }
349 } else {
350 // FeatureConnector doesn't report UNAVAILABLE_REASON_IMS_UNSUPPORTED,
351 // so report the reason here.
352 connectionUnavailable(UNAVAILABLE_REASON_IMS_UNSUPPORTED);
353 }
354 }
355
356 // called from onRegisterCallback
357 boolean notifyState(CallbackWrapper wrapper) {
358 if (VDBG) logv(mLogPrefix + "notifyState subId=" + wrapper.mSubId);
359
360 return wrapper.notifyState(mSubId, FEATURE_MMTEL, mState, mReason);
361 }
362
363 void dump(IndentingPrintWriter pw) {
364 pw.println("Listener={slotId=" + mSlotId
365 + ", subId=" + mSubId
366 + ", state=" + ImsFeature.STATE_LOG_MAP.get(mState)
367 + ", reason=" + imsStateReasonToString(mReason)
368 + ", hasConfig=" + mHasConfig
369 + "}");
370 }
371 }
372
373 private final class RcsFeatureListener implements FeatureConnector.Listener<RcsFeatureManager> {
374 private FeatureConnector<RcsFeatureManager> mConnector;
375 private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
376 private int mState = STATE_UNAVAILABLE;
377 private int mReason = REASON_IMS_SERVICE_DISCONNECTED;
378
379 /*
380 * Remember the last return of verifyImsMmTelConfigured().
381 * true means ImsResolver found an IMS package for FEATURE_RCS.
382 *
383 * mReason is updated through connectionUnavailable triggered by ImsResolver.
384 * mHasConfig is update through notifyConfigChanged triggered by mReceiver,
385 * and notifyExternalRcsState which triggered by TelephonyRcsService refers it.
386 * mHasConfig can be a redundancy of (mReason == REASON_NO_IMS_SERVICE_CONFIGURED).
387 * However, when a carrier config changes, we are not sure the order
388 * of execution of connectionUnavailable, notifyConfigChanged and notifyExternalRcsState.
389 * So, it's safe to use a separated state to retain it.
390 * We assume mHasConfig is true, until it's determined explicitly.
391 */
392 private boolean mHasConfig = true;
393
394 /*
395 * TelephonyRcsService doesn’t try to connect to RcsFeature if there is no active feature
396 * for a given subscription. The active features are declared by carrier configs and
397 * configuration resources. The APIs of ImsRcsManager and SipDelegateManager are available
398 * only when the RcsFeatureController has a STATE_READY state connection.
399 * This configuration is different from the configuration of IMS package for RCS.
400 * ImsStateCallbackController's FeatureConnectorListener can be STATE_READY state,
401 * even in case there is no active RCS feature. But Manager's APIs throws exception.
402 *
403 * For RCS, in addition to mHasConfig, the sate of TelephonyRcsService and
404 * RcsFeatureConnector will be traced to determine the state to be notified to clients.
405 */
406 private ExternalRcsFeatureState mExternalState = null;
407
408 private int mSlotId = -1;
409 private String mLogPrefix = "";
410
411 RcsFeatureListener(int slotId) {
412 mSlotId = slotId;
413 mLogPrefix = "[" + slotId + ", RCS] ";
414 if (VDBG) logv(mLogPrefix + "created");
415
416 mConnector = mRcsFeatureFactory.create(
417 mApp, slotId, this, new HandlerExecutor(mHandler), TAG);
418 mConnector.connect();
419 }
420
421 void setSubId(int subId) {
422 if (VDBG) logv(mLogPrefix + "setSubId mSubId=" + mSubId + ", subId=" + subId);
423 if (mSubId == subId) return;
424 logd(mLogPrefix + "setSubId changed subId=" + subId);
425
426 mSubId = subId;
427 }
428
429 void destroy() {
430 if (VDBG) logv(mLogPrefix + "destroy");
431
432 mConnector.disconnect();
433 mConnector = null;
434 }
435
436 @Override
437 public void connectionReady(RcsFeatureManager manager) {
438 logd(mLogPrefix + "connectionReady");
439
440 mState = STATE_READY;
441 mReason = AVAILABLE;
442 mHasConfig = true;
443
444 if (mExternalState != null && mExternalState.isReady()) {
445 onFeatureStateChange(mSubId, FEATURE_RCS, mState, mReason);
446 }
447 }
448
449 @Override
450 public void connectionUnavailable(int reason) {
451 logd(mLogPrefix + "connectionUnavailable reason=" + connectorReasonToString(reason));
452
453 reason = convertReasonType(reason);
454 if (mReason == reason) return;
455
456 connectionUnavailableInternal(reason);
457 }
458
459 private void connectionUnavailableInternal(int reason) {
460 mState = STATE_UNAVAILABLE;
461 mReason = reason;
462
463 /* If having no IMS package for RCS,
464 * dicard the reason except REASON_NO_IMS_SERVICE_CONFIGURED. */
465 if (!mHasConfig && reason != REASON_NO_IMS_SERVICE_CONFIGURED) return;
466
467 if (mExternalState == null && reason != REASON_NO_IMS_SERVICE_CONFIGURED) {
468 // Wait until TelephonyRcsService notifies its state.
469 return;
470 }
471
472 if (mExternalState != null && !mExternalState.hasActiveFeatures()) {
473 // notifyExternalRcsState has notified REASON_NO_IMS_SERVICE_CONFIGURED already
474 // ignore it
475 return;
476 }
477
478 if ((mExternalState != null && mExternalState.hasActiveFeatures())
479 || mReason == REASON_NO_IMS_SERVICE_CONFIGURED) {
480 onFeatureStateChange(mSubId, FEATURE_RCS, mState, mReason);
481 }
482 }
483
484 void notifyConfigChanged(boolean hasConfig) {
485 if (mHasConfig == hasConfig) return;
486
487 logd(mLogPrefix + "notifyConfigChanged " + hasConfig);
488
489 mHasConfig = hasConfig;
490 if (hasConfig) {
491 // REASON_NO_IMS_SERVICE_CONFIGURED is already reported to the clients,
492 // since there is no configuration of IMS package for RCS.
493 // Now, a carrier configuration change is notified and
494 // the response from ImsResolver is changed from false to true.
495 if (mState != STATE_READY) {
496 if (mReason == REASON_NO_IMS_SERVICE_CONFIGURED) {
497 // In this case, notify clients the reason, REASON_DISCONNCTED,
498 // to update the state.
499 connectionUnavailable(UNAVAILABLE_REASON_DISCONNECTED);
500 } else {
501 // ImsResolver and ImsStateCallbackController run with different Looper.
502 // In this case, FeatureConnectorListener is updated ahead of this.
503 // But, connectionUnavailable didn't notify clients since mHasConfig is
504 // false. So, notify clients here.
505 connectionUnavailableInternal(mReason);
506 }
507 }
508 } else {
509 // FeatureConnector doesn't report UNAVAILABLE_REASON_IMS_UNSUPPORTED,
510 // so report the reason here.
511 connectionUnavailable(UNAVAILABLE_REASON_IMS_UNSUPPORTED);
512 }
513 }
514
515 void notifyExternalRcsState(ExternalRcsFeatureState fs) {
516 if (VDBG) {
517 logv(mLogPrefix + "notifyExternalRcsState"
518 + " state=" + (fs.mState == STATE_UNKNOWN
519 ? "" : ImsFeature.STATE_LOG_MAP.get(fs.mState))
520 + ", reason=" + imsStateReasonToString(fs.mReason));
521 }
522
523 ExternalRcsFeatureState oldFs = mExternalState;
524 // External state is from TelephonyRcsService while a feature is added or removed.
525 if (fs.mState == STATE_UNKNOWN) {
526 if (oldFs != null) fs.mState = oldFs.mState;
527 else fs.mState = STATE_UNAVAILABLE;
528 }
529
530 mExternalState = fs;
531
532 // No IMS package found.
533 // REASON_NO_IMS_SERVICE_CONFIGURED is notified to clients already.
534 if (!mHasConfig) return;
535
536 if (fs.hasActiveFeatures()) {
537 if (mState == STATE_READY) {
538 if ((oldFs == null || !oldFs.isReady()) && fs.isReady()) {
539 // it is waiting RcsFeatureConnector's notification.
540 // notify clients here.
541 onFeatureStateChange(mSubId, FEATURE_RCS, mState, mReason);
542 } else if (!fs.isReady()) {
543 // Wait RcsFeatureConnector's notification
544 } else {
545 // ignore duplicated notification
546 }
547 }
548 } else {
549 // notify only once
550 if (oldFs == null || oldFs.hasActiveFeatures()) {
551 if (mReason != REASON_NO_IMS_SERVICE_CONFIGURED) {
552 onFeatureStateChange(
553 mSubId, FEATURE_RCS, STATE_UNAVAILABLE,
554 REASON_NO_IMS_SERVICE_CONFIGURED);
555 }
556 } else {
557 // ignore duplicated notification
558 }
559 }
560 }
561
562 // called from onRegisterCallback
563 boolean notifyState(CallbackWrapper wrapper) {
564 if (VDBG) logv(mLogPrefix + "notifyState subId=" + wrapper.mSubId);
565
566 if (mHasConfig) {
567 if (mExternalState == null) {
568 // Wait until TelephonyRcsService notifies its state.
569 return wrapper.notifyState(mSubId, FEATURE_RCS, STATE_UNAVAILABLE,
570 REASON_IMS_SERVICE_DISCONNECTED);
571 } else if (!mExternalState.hasActiveFeatures()) {
572 return wrapper.notifyState(mSubId, FEATURE_RCS, STATE_UNAVAILABLE,
573 REASON_NO_IMS_SERVICE_CONFIGURED);
574 }
575 }
576
577 return wrapper.notifyState(mSubId, FEATURE_RCS, mState, mReason);
578 }
579
580 void dump(IndentingPrintWriter pw) {
581 pw.println("Listener={slotId=" + mSlotId
582 + ", subId=" + mSubId
583 + ", state=" + ImsFeature.STATE_LOG_MAP.get(mState)
584 + ", reason=" + imsStateReasonToString(mReason)
585 + ", hasConfig=" + mHasConfig
586 + ", isReady=" + (mExternalState == null ? false : mExternalState.isReady())
587 + ", hasFeatures=" + (mExternalState == null ? false
588 : mExternalState.hasActiveFeatures())
589 + "}");
590 }
591 }
592
593 /**
594 * A wrapper class for the callback registered
595 */
596 private static class CallbackWrapper {
597 private final int mSubId;
598 private final int mRequiredFeature;
599 private final IImsStateCallback mCallback;
600 private final IBinder mBinder;
601 private final String mCallingPackage;
602 private int mLastReason = NOT_INITIALIZED;
603
604 CallbackWrapper(int subId, int feature, IImsStateCallback callback,
605 String callingPackage) {
606 mSubId = subId;
607 mRequiredFeature = feature;
608 mCallback = callback;
609 mBinder = callback.asBinder();
610 mCallingPackage = callingPackage;
611 }
612
613 /**
614 * @return false when accessing callback binder throws an Exception.
615 * That means the callback binder is not valid any longer.
616 * The death of remote process can cause this.
617 * This instance shall be removed from the list.
618 */
619 boolean notifyState(int subId, int feature, int state, int reason) {
620 if (VDBG) {
621 logv("CallbackWrapper notifyState subId=" + subId
622 + ", feature=" + ImsFeature.FEATURE_LOG_MAP.get(feature)
623 + ", state=" + ImsFeature.STATE_LOG_MAP.get(state)
624 + ", reason=" + imsStateReasonToString(reason));
625 }
626
627 try {
628 if (state == STATE_READY) {
629 mCallback.onAvailable();
630 } else {
631 mCallback.onUnavailable(reason);
632 }
633 mLastReason = reason;
634 } catch (Exception e) {
635 loge("CallbackWrapper notifyState e=" + e);
636 return false;
637 }
638
639 return true;
640 }
641
642 void notifyInactive() {
643 if (VDBG) logv("CallbackWrapper notifyInactive subId=" + mSubId);
644
645 try {
646 mCallback.onUnavailable(REASON_SUBSCRIPTION_INACTIVE);
647 } catch (Exception e) {
648 // ignored
649 }
650 }
651
652 void dump(IndentingPrintWriter pw) {
653 pw.println("CallbackWrapper={subId=" + mSubId
654 + ", feature=" + ImsFeature.FEATURE_LOG_MAP.get(mRequiredFeature)
655 + ", reason=" + imsStateReasonToString(mLastReason)
656 + ", pkg=" + mCallingPackage
657 + "}");
658 }
659 }
660
661 private static class ExternalRcsFeatureState {
662 private int mSlotId;
663 private int mState = STATE_UNAVAILABLE;
664 private int mReason = NOT_INITIALIZED;
665
666 ExternalRcsFeatureState(int slotId, int state, int reason) {
667 mSlotId = slotId;
668 mState = state;
669 mReason = reason;
670 }
671
672 boolean hasActiveFeatures() {
673 return mReason != REASON_NO_IMS_SERVICE_CONFIGURED;
674 }
675
676 boolean isReady() {
677 return mState == STATE_READY;
678 }
679 }
680
681 /**
682 * create an instance
683 */
684 public static ImsStateCallbackController make(PhoneGlobals app, int numSlots) {
685 synchronized (ImsStateCallbackController.class) {
686 if (sInstance == null) {
687 logd("ImsStateCallbackController created");
688
689 HandlerThread handlerThread = new HandlerThread(TAG);
690 handlerThread.start();
691 sInstance = new ImsStateCallbackController(app, handlerThread.getLooper(), numSlots,
692 ImsManager::getConnector, RcsFeatureManager::getConnector,
693 ImsResolver.getInstance());
694 }
695 }
696 return sInstance;
697 }
698
699 @VisibleForTesting
700 public ImsStateCallbackController(PhoneGlobals app, Looper looper, int numSlots,
701 MmTelFeatureConnectorFactory mmTelFactory, RcsFeatureConnectorFactory rcsFactory,
702 ImsResolver imsResolver) {
703 mApp = app;
704 mHandler = new MyHandler(looper);
705 mImsResolver = imsResolver;
706 mSubscriptionManager = mApp.getSystemService(SubscriptionManager.class);
707 mTelephonyRegistryManager = mApp.getSystemService(TelephonyRegistryManager.class);
708 mMmTelFeatureFactory = mmTelFactory;
709 mRcsFeatureFactory = rcsFactory;
710
711 updateFeatureControllerSize(numSlots);
712
713 mTelephonyRegistryManager.addOnSubscriptionsChangedListener(
714 mSubChangedListener, mSubChangedListener.getHandlerExecutor());
715
716 PhoneConfigurationManager.registerForMultiSimConfigChange(mHandler,
717 EVENT_MSIM_CONFIGURATION_CHANGE, null);
718
719 mApp.registerReceiver(mReceiver, new IntentFilter(
720 CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
721
722 onSubChanged();
723 }
724
725 /**
726 * Update the number of {@link RcsFeatureController}s that are created based on the number of
727 * active slots on the device.
728 */
729 @VisibleForTesting
730 public void updateFeatureControllerSize(int newNumSlots) {
731 if (mNumSlots != newNumSlots) {
732 logd("updateFeatures: oldSlots=" + mNumSlots
733 + ", newNumSlots=" + newNumSlots);
734 if (mNumSlots < newNumSlots) {
735 for (int i = mNumSlots; i < newNumSlots; i++) {
736 MmTelFeatureListener m = new MmTelFeatureListener(i);
737 mMmTelFeatureListeners.put(i, m);
738 RcsFeatureListener r = new RcsFeatureListener(i);
739 mRcsFeatureListeners.put(i, r);
740 }
741 } else {
742 for (int i = (mNumSlots - 1); i > (newNumSlots - 1); i--) {
743 MmTelFeatureListener m = mMmTelFeatureListeners.get(i);
744 if (m != null) {
745 mMmTelFeatureListeners.remove(i);
746 m.destroy();
747 }
748 RcsFeatureListener r = mRcsFeatureListeners.get(i);
749 if (r != null) {
750 mRcsFeatureListeners.remove(i);
751 r.destroy();
752 }
753 }
754 }
755 }
756 mNumSlots = newNumSlots;
757 }
758
759 /**
760 * Dependencies for testing.
761 */
762 @VisibleForTesting
763 public void onSubChanged() {
764 for (int i = 0; i < mMmTelFeatureListeners.size(); i++) {
765 MmTelFeatureListener l = mMmTelFeatureListeners.valueAt(i);
766 l.setSubId(getSubId(i));
767 }
768
769 for (int i = 0; i < mRcsFeatureListeners.size(); i++) {
770 RcsFeatureListener l = mRcsFeatureListeners.valueAt(i);
771 l.setSubId(getSubId(i));
772 }
773
774 if (mWrappers.size() == 0) return;
775
776 ArrayList<IBinder> inactiveCallbacks = new ArrayList<>();
777 final int[] activeSubs = mSubscriptionManager.getActiveSubscriptionIdList();
778
779 if (VDBG) logv("onSubChanged activeSubs=" + Arrays.toString(activeSubs));
780
781 // Remove callbacks for inactive subscriptions
782 for (IBinder binder : mWrappers.keySet()) {
783 CallbackWrapper wrapper = mWrappers.get(binder);
784 if (wrapper != null) {
785 if (!isActive(activeSubs, wrapper.mSubId)) {
786 // inactive subscription
787 inactiveCallbacks.add(binder);
788 }
789 } else {
790 // unexpected, remove it
791 inactiveCallbacks.add(binder);
792 }
793 }
794 removeInactiveCallbacks(inactiveCallbacks, "onSubChanged");
795 }
796
797 private void onFeatureStateChange(int subId, int feature, int state, int reason) {
798 if (VDBG) {
799 logv("onFeatureStateChange subId=" + subId
800 + ", feature=" + ImsFeature.FEATURE_LOG_MAP.get(feature)
801 + ", state=" + ImsFeature.STATE_LOG_MAP.get(state)
802 + ", reason=" + imsStateReasonToString(reason));
803 }
804
805 ArrayList<IBinder> inactiveCallbacks = new ArrayList<>();
806 mWrappers.values().forEach(wrapper -> {
807 if (subId == wrapper.mSubId
808 && feature == wrapper.mRequiredFeature
809 && !wrapper.notifyState(subId, feature, state, reason)) {
810 // callback has exception, remove it
811 inactiveCallbacks.add(wrapper.mBinder);
812 }
813 });
814 removeInactiveCallbacks(inactiveCallbacks, "onFeatureStateChange");
815 }
816
817 private void onRegisterCallback(CallbackWrapper wrapper) {
818 if (wrapper == null) return;
819
820 if (VDBG) logv("onRegisterCallback before size=" + mWrappers.size());
821 if (VDBG) {
822 logv("onRegisterCallback subId=" + wrapper.mSubId
823 + ", feature=" + wrapper.mRequiredFeature);
824 }
825
826 // Not sure the following case can happen or not:
827 // step1) Subscription changed
828 // step2) ImsStateCallbackController not processed onSubChanged yet
829 // step3) Client registers with a strange subId
830 // The validity of the subId is checked PhoneInterfaceManager#registerImsStateCallback.
831 // So, register the wrapper here before trying to notifyState.
832 // TODO: implement the recovery for this case, notifying the current reson, in onSubChanged
833 mWrappers.put(wrapper.mBinder, wrapper);
834
835 if (wrapper.mRequiredFeature == FEATURE_MMTEL) {
836 for (int i = 0; i < mMmTelFeatureListeners.size(); i++) {
837 MmTelFeatureListener l = mMmTelFeatureListeners.valueAt(i);
838 if (l.mSubId == wrapper.mSubId
839 && !l.notifyState(wrapper)) {
840 mWrappers.remove(wrapper.mBinder);
841 break;
842 }
843 }
844 } else if (wrapper.mRequiredFeature == FEATURE_RCS) {
845 for (int i = 0; i < mRcsFeatureListeners.size(); i++) {
846 RcsFeatureListener l = mRcsFeatureListeners.valueAt(i);
847 if (l.mSubId == wrapper.mSubId
848 && !l.notifyState(wrapper)) {
849 mWrappers.remove(wrapper.mBinder);
850 break;
851 }
852 }
853 }
854
855 if (VDBG) logv("onRegisterCallback after size=" + mWrappers.size());
856 }
857
858 private void onUnregisterCallback(IImsStateCallback cb) {
859 if (cb == null) return;
860 mWrappers.remove(cb.asBinder());
861 }
862
863 private void onCarrierConfigChanged(int slotId) {
864 if (slotId >= mNumSlots) {
865 logd("onCarrierConfigChanged invalid slotId "
866 + slotId + ", mNumSlots=" + mNumSlots);
867 return;
868 }
869
870 logv("onCarrierConfigChanged slotId=" + slotId);
871
872 boolean hasConfig = verifyImsMmTelConfigured(slotId);
873 if (slotId < mMmTelFeatureListeners.size()) {
874 MmTelFeatureListener listener = mMmTelFeatureListeners.valueAt(slotId);
875 listener.notifyConfigChanged(hasConfig);
876 }
877
878 hasConfig = verifyImsRcsConfigured(slotId);
879 if (slotId < mRcsFeatureListeners.size()) {
880 RcsFeatureListener listener = mRcsFeatureListeners.valueAt(slotId);
881 listener.notifyConfigChanged(hasConfig);
882 }
883 }
884
885 private void onExternalRcsStateChanged(ExternalRcsFeatureState fs) {
886 logv("onExternalRcsStateChanged slotId=" + fs.mSlotId
887 + ", state=" + (fs.mState == STATE_UNKNOWN
888 ? "" : ImsFeature.STATE_LOG_MAP.get(fs.mState))
889 + ", reason=" + imsStateReasonToString(fs.mReason));
890
891 RcsFeatureListener listener = mRcsFeatureListeners.get(fs.mSlotId);
892 if (listener != null) {
893 listener.notifyExternalRcsState(fs);
894 } else {
895 // unexpected state
896 loge("onExternalRcsStateChanged slotId=" + fs.mSlotId + ", no listener.");
897 }
898 }
899
900 /**
901 * Interface to be notified from TelephonyRcsSerice and RcsFeatureController
902 *
903 * @param ready true if feature's state is STATE_READY. Valid only when it is true.
904 * @param hasActiveFeatures true if the RcsFeatureController has active features.
905 */
906 public void notifyExternalRcsStateChanged(
907 int slotId, boolean ready, boolean hasActiveFeatures) {
908 int state = STATE_UNKNOWN;
909 int reason = REASON_IMS_SERVICE_DISCONNECTED;
910
911 if (ready) {
912 // From RcsFeatureController
913 state = STATE_READY;
914 reason = AVAILABLE;
915 } else if (!hasActiveFeatures) {
916 // From TelephonyRcsService
917 reason = REASON_NO_IMS_SERVICE_CONFIGURED;
918 state = STATE_UNAVAILABLE;
919 } else {
920 // From TelephonyRcsService
921 // TelephonyRcsService doesn't know the exact state of FeatureConnection.
922 // Only when there is no feature, we can assume the state.
923 }
924
925 if (VDBG) {
926 logv("notifyExternalRcsStateChanged slotId=" + slotId
927 + ", ready=" + ready
928 + ", hasActiveFeatures=" + hasActiveFeatures);
929 }
930
931 ExternalRcsFeatureState fs = new ExternalRcsFeatureState(slotId, state, reason);
932 mHandler.sendMessage(mHandler.obtainMessage(EVENT_EXTERNAL_RCS_STATE_CHANGED, fs));
933 }
934
935 /**
936 * Notifies carrier configuration has changed.
937 */
938 @VisibleForTesting
939 public void notifyCarrierConfigChanged(int slotId) {
940 if (VDBG) logv("notifyCarrierConfigChanged slotId=" + slotId);
941 mHandler.sendMessage(mHandler.obtainMessage(EVENT_CARRIER_CONFIG_CHANGED, slotId, 0));
942 }
943 /**
944 * Register IImsStateCallback
945 *
946 * @param feature for which state is changed, ImsFeature.FEATURE_*
947 */
948 public void registerImsStateCallback(int subId, int feature, IImsStateCallback cb,
949 String callingPackage) {
950 if (VDBG) {
951 logv("registerImsStateCallback subId=" + subId
952 + ", feature=" + feature + ", pkg=" + callingPackage);
953 }
954
955 CallbackWrapper wrapper = new CallbackWrapper(subId, feature, cb, callingPackage);
956 mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_CALLBACK, wrapper));
957 }
958
959 /**
960 * Unegister previously registered callback
961 */
962 public void unregisterImsStateCallback(IImsStateCallback cb) {
963 if (VDBG) logv("unregisterImsStateCallback");
964
965 mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_CALLBACK, cb));
966 }
967
968 private void removeInactiveCallbacks(
969 ArrayList<IBinder> inactiveCallbacks, String message) {
970 if (inactiveCallbacks == null || inactiveCallbacks.size() == 0) return;
971
972 if (VDBG) {
973 logv("removeInactiveCallbacks size="
974 + inactiveCallbacks.size() + " from " + message);
975 }
976
977 for (IBinder binder : inactiveCallbacks) {
978 CallbackWrapper wrapper = mWrappers.get(binder);
979 if (wrapper != null) {
980 // Send the reason REASON_SUBSCRIPTION_INACTIVE to the client
981 wrapper.notifyInactive();
982 mWrappers.remove(binder);
983 }
984 }
985 inactiveCallbacks.clear();
986 }
987
988 private int getSubId(int slotId) {
989 Phone phone = mPhoneFactoryProxy.getPhone(slotId);
990 if (phone != null) return phone.getSubId();
991 return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
992 }
993
994 private static boolean isActive(final int[] activeSubs, int subId) {
995 for (int i : activeSubs) {
996 if (i == subId) return true;
997 }
998 return false;
999 }
1000
1001 private static int convertReasonType(int reason) {
1002 switch(reason) {
1003 case UNAVAILABLE_REASON_NOT_READY:
1004 return REASON_IMS_SERVICE_NOT_READY;
1005 case UNAVAILABLE_REASON_IMS_UNSUPPORTED:
1006 return REASON_NO_IMS_SERVICE_CONFIGURED;
1007 default:
1008 break;
1009 }
1010
1011 return REASON_IMS_SERVICE_DISCONNECTED;
1012 }
1013
1014 private boolean verifyImsMmTelConfigured(int slotId) {
1015 boolean ret = false;
1016 if (mImsResolver == null) {
1017 loge("verifyImsMmTelConfigured mImsResolver is null");
1018 } else {
1019 ret = mImsResolver.isImsServiceConfiguredForFeature(slotId, FEATURE_MMTEL);
1020 }
1021 if (VDBG) logv("verifyImsMmTelConfigured slotId=" + slotId + ", ret=" + ret);
1022 return ret;
1023 }
1024
1025 private boolean verifyImsRcsConfigured(int slotId) {
1026 boolean ret = false;
1027 if (mImsResolver == null) {
1028 loge("verifyImsRcsConfigured mImsResolver is null");
1029 } else {
1030 ret = mImsResolver.isImsServiceConfiguredForFeature(slotId, FEATURE_RCS);
1031 }
1032 if (VDBG) logv("verifyImsRcsConfigured slotId=" + slotId + ", ret=" + ret);
1033 return ret;
1034 }
1035
1036 private static String connectorReasonToString(int reason) {
1037 switch(reason) {
1038 case UNAVAILABLE_REASON_DISCONNECTED:
1039 return "DISCONNECTED";
1040 case UNAVAILABLE_REASON_NOT_READY:
1041 return "NOT_READY";
1042 case UNAVAILABLE_REASON_IMS_UNSUPPORTED:
1043 return "IMS_UNSUPPORTED";
1044 case UNAVAILABLE_REASON_SERVER_UNAVAILABLE:
1045 return "SERVER_UNAVAILABLE";
1046 default:
1047 break;
1048 }
1049 return "";
1050 }
1051
1052 private static String imsStateReasonToString(int reason) {
1053 switch(reason) {
1054 case AVAILABLE:
1055 return "READY";
1056 case REASON_UNKNOWN_TEMPORARY_ERROR:
1057 return "UNKNOWN_TEMPORARY_ERROR";
1058 case REASON_UNKNOWN_PERMANENT_ERROR:
1059 return "UNKNOWN_PERMANENT_ERROR";
1060 case REASON_IMS_SERVICE_DISCONNECTED:
1061 return "IMS_SERVICE_DISCONNECTED";
1062 case REASON_NO_IMS_SERVICE_CONFIGURED:
1063 return "NO_IMS_SERVICE_CONFIGURED";
1064 case REASON_SUBSCRIPTION_INACTIVE:
1065 return "SUBSCRIPTION_INACTIVE";
1066 case REASON_IMS_SERVICE_NOT_READY:
1067 return "IMS_SERVICE_NOT_READY";
1068 default:
1069 break;
1070 }
1071 return "";
1072 }
1073
1074 /**
1075 * PhoneFactory Dependencies for testing.
1076 */
1077 @VisibleForTesting
1078 public interface PhoneFactoryProxy {
1079 /**
1080 * Override getPhone for testing.
1081 */
1082 Phone getPhone(int index);
1083 }
1084
1085 private PhoneFactoryProxy mPhoneFactoryProxy = new PhoneFactoryProxy() {
1086 @Override
1087 public Phone getPhone(int index) {
1088 return PhoneFactory.getPhone(index);
1089 }
1090 };
1091
1092 private void release() {
1093 if (VDBG) logv("release");
1094
1095 mTelephonyRegistryManager.removeOnSubscriptionsChangedListener(mSubChangedListener);
1096 mApp.unregisterReceiver(mReceiver);
1097
1098 for (int i = 0; i < mMmTelFeatureListeners.size(); i++) {
1099 mMmTelFeatureListeners.valueAt(i).destroy();
1100 }
1101 mMmTelFeatureListeners.clear();
1102
1103 for (int i = 0; i < mRcsFeatureListeners.size(); i++) {
1104 mRcsFeatureListeners.valueAt(i).destroy();
1105 }
1106 mRcsFeatureListeners.clear();
1107 }
1108
1109 /**
1110 * destroy the instance
1111 */
1112 @VisibleForTesting
1113 public void destroy() {
1114 if (VDBG) logv("destroy it");
1115
1116 release();
1117 mHandler.getLooper().quit();
1118 }
1119
1120 /**
1121 * get the handler
1122 */
1123 @VisibleForTesting
1124 public Handler getHandler() {
1125 return mHandler;
1126 }
1127
1128 /**
1129 * Determine whether the callback is registered or not
1130 */
1131 @VisibleForTesting
1132 public boolean isRegistered(IImsStateCallback cb) {
1133 if (cb == null) return false;
1134 return mWrappers.containsKey(cb.asBinder());
1135 }
1136
1137 /**
1138 * Dump this instance into a readable format for dumpsys usage.
1139 */
1140 public void dump(IndentingPrintWriter pw) {
1141 pw.increaseIndent();
1142 synchronized (mDumpLock) {
1143 pw.println("CallbackWrappers:");
1144 pw.increaseIndent();
1145 mWrappers.values().forEach(wrapper -> wrapper.dump(pw));
1146 pw.decreaseIndent();
1147 pw.println("MmTelFeatureListeners:");
1148 pw.increaseIndent();
1149 for (int i = 0; i < mNumSlots; i++) {
1150 MmTelFeatureListener l = mMmTelFeatureListeners.get(i);
1151 if (l == null) continue;
1152 l.dump(pw);
1153 }
1154 pw.decreaseIndent();
1155 pw.println("RcsFeatureListeners:");
1156 pw.increaseIndent();
1157 for (int i = 0; i < mNumSlots; i++) {
1158 RcsFeatureListener l = mRcsFeatureListeners.get(i);
1159 if (l == null) continue;
1160 l.dump(pw);
1161 }
1162 pw.decreaseIndent();
1163 pw.println("Most recent logs:");
1164 pw.increaseIndent();
1165 sLocalLog.dump(pw);
1166 pw.decreaseIndent();
1167 }
1168 pw.decreaseIndent();
1169 }
1170
1171 private static void logv(String msg) {
1172 Rlog.d(TAG, msg);
1173 }
1174
1175 private static void logd(String msg) {
1176 Rlog.d(TAG, msg);
1177 sLocalLog.log(msg);
1178 }
1179
1180 private static void loge(String msg) {
1181 Rlog.e(TAG, msg);
1182 sLocalLog.log(msg);
1183 }
1184}