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