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