blob: d29a08080cb2a5f25cff6081005e7966cf5a8d46 [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
108 /** The unavailable reason of ImsFeature is not initialized */
109 private static final int NOT_INITIALIZED = -1;
110 /** The ImsFeature is available. */
111 private static final int AVAILABLE = 0;
112
113 private static final int EVENT_SUB_CHANGED = 1;
114 private static final int EVENT_REGISTER_CALLBACK = 2;
115 private static final int EVENT_UNREGISTER_CALLBACK = 3;
116 private static final int EVENT_CARRIER_CONFIG_CHANGED = 4;
117
118 private static ImsStateCallbackController sInstance;
119
120 /**
121 * get the instance
122 */
123 public static ImsStateCallbackController getInstance() {
124 synchronized (ImsStateCallbackController.class) {
125 return sInstance;
126 }
127 }
128
129 private final PhoneGlobals mApp;
130 private final Handler mHandler;
131 private final ImsResolver mImsResolver;
132 private final SparseArray<MmTelFeatureListener> mMmTelFeatureListeners = new SparseArray<>();
133 private final SparseArray<RcsFeatureListener> mRcsFeatureListeners = new SparseArray<>();
134
135 private final SubscriptionManager mSubscriptionManager;
136 private final TelephonyRegistryManager mTelephonyRegistryManager;
137 private MmTelFeatureConnectorFactory mMmTelFeatureFactory;
138 private RcsFeatureConnectorFactory mRcsFeatureFactory;
139
Hunsuk Choi60215822021-11-01 07:19:30 +0000140 private HashMap<IBinder, CallbackWrapper> mWrappers = new HashMap<>();
Hunsuk Choi3b742d62021-10-25 19:48:34 +0000141
142 private int mNumSlots;
143
144 private BroadcastReceiver mReceiver = new BroadcastReceiver() {
145 @Override
146 public void onReceive(Context context, Intent intent) {
147 if (intent == null) {
148 return;
149 }
150 if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
151 Bundle bundle = intent.getExtras();
152 if (bundle == null) {
153 return;
154 }
155 int slotId = bundle.getInt(CarrierConfigManager.EXTRA_SLOT_INDEX,
156 SubscriptionManager.INVALID_PHONE_INDEX);
157 int subId = bundle.getInt(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
158 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
159
160 if (slotId <= SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
161 loge("onReceive ACTION_CARRIER_CONFIG_CHANGED invalid slotId");
162 return;
163 }
164
165 if (subId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
166 loge("onReceive ACTION_CARRIER_CONFIG_CHANGED invalid subId");
167 //subscription changed will be notified by mSubChangedListener
168 return;
169 }
170
171 notifyCarrierConfigChanged(slotId);
172 }
173 }
174 };
175
176 private final SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener =
177 new SubscriptionManager.OnSubscriptionsChangedListener() {
178 @Override
179 public void onSubscriptionsChanged() {
180 if (!mHandler.hasMessages(EVENT_SUB_CHANGED)) {
181 mHandler.sendEmptyMessage(EVENT_SUB_CHANGED);
182 }
183 }
184 };
185
186 private final class MyHandler extends Handler {
187 MyHandler(Looper looper) {
188 super(looper);
189 }
190
191 @Override
192 public void handleMessage(Message msg) {
193 logv("handleMessage: " + msg);
194 switch (msg.what) {
195 case EVENT_SUB_CHANGED:
196 onSubChanged();
197 break;
198
199 case EVENT_REGISTER_CALLBACK:
200 onRegisterCallback((ImsStateCallbackController.CallbackWrapper) msg.obj);
201 break;
202
203 case EVENT_UNREGISTER_CALLBACK:
204 onUnregisterCallback((IImsStateCallback) msg.obj);
205 break;
206
207 case EVENT_CARRIER_CONFIG_CHANGED:
208 onCarrierConfigChanged(msg.arg1);
209 break;
210
211 default:
212 loge("Unhandled event " + msg.what);
213 }
214 }
215 }
216
217 private final class MmTelFeatureListener implements FeatureConnector.Listener<ImsManager> {
218 private FeatureConnector<ImsManager> mConnector;
219 private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
220 private int mState = STATE_UNAVAILABLE;
221 private int mReason = REASON_IMS_SERVICE_DISCONNECTED;
222 /**
223 * Remember the last return of verifyImsMmTelConfigured().
224 * true means ImsResolver found an IMS package for FEATURE_MMTEL.
225 */
226 private boolean mHasConfig = true;
227
228 private int mSlotId = -1;
229 private String mLogPrefix = "";
230
231 MmTelFeatureListener(int slotId) {
232 mLogPrefix = "[MMTEL, " + slotId + "] ";
233 logv(mLogPrefix + "create");
234 mConnector = mMmTelFeatureFactory.create(
235 mApp, slotId, TAG, this, new HandlerExecutor(mHandler));
236 mConnector.connect();
237 }
238
239 void setSubId(int subId) {
240 logv(mLogPrefix + "setSubId mSubId=" + mSubId + ", subId=" + subId);
241 if (mSubId == subId) return;
242 logd(mLogPrefix + "setSubId subId changed");
243
244 mSubId = subId;
245 }
246
247 void destroy() {
248 logv(mLogPrefix + "destroy");
249 mConnector.disconnect();
250 mConnector = null;
251 }
252
253 @Override
254 public void connectionReady(ImsManager manager) {
255 logd(mLogPrefix + "connectionReady");
256
257 mState = STATE_READY;
258 mReason = AVAILABLE;
259 mHasConfig = true;
260 onFeatureStateChange(mSubId, FEATURE_MMTEL, mState, mReason);
261 }
262
263 @Override
264 public void connectionUnavailable(int reason) {
265 logd(mLogPrefix + "connectionUnavailable reason=" + connectorReasonToString(reason));
266
267 reason = convertReasonType(reason);
268 if (mReason == reason) return;
269
270 mState = STATE_UNAVAILABLE;
271 /* If having no IMS package for MMTEL,
272 * dicard the reason except REASON_NO_IMS_SERVICE_CONFIGURED. */
273 if (!mHasConfig && reason != REASON_NO_IMS_SERVICE_CONFIGURED) return;
274 mReason = reason;
275
276 onFeatureStateChange(mSubId, FEATURE_MMTEL, mState, mReason);
277 }
278
279 void notifyConfigChanged(boolean hasConfig) {
280 if (mHasConfig == hasConfig) return;
281
282 logd(mLogPrefix + "notifyConfigChanged " + hasConfig);
283
284 mHasConfig = hasConfig;
285 if (hasConfig) {
286 // REASON_NO_IMS_SERVICE_CONFIGURED is already reported to the clients,
287 // since there is no configuration of IMS package for MMTEL.
288 // Now, a carrier configuration change is notified and
289 // mHasConfig is changed from false to true.
290 // In this case, notify clients the reason, REASON_DISCONNCTED,
291 // to update the state.
292 if (mState != STATE_READY && mReason == REASON_NO_IMS_SERVICE_CONFIGURED) {
293 connectionUnavailable(UNAVAILABLE_REASON_DISCONNECTED);
294 }
295 } else {
296 // FeatureConnector doesn't report UNAVAILABLE_REASON_IMS_UNSUPPORTED,
297 // so report the reason here.
298 connectionUnavailable(UNAVAILABLE_REASON_IMS_UNSUPPORTED);
299 }
300 }
301
302 // called from onRegisterCallback
303 boolean notifyState(CallbackWrapper wrapper) {
304 logv(mLogPrefix + "notifyState subId=" + wrapper.mSubId);
305
306 return wrapper.notifyState(mSubId, FEATURE_MMTEL, mState, mReason);
307 }
308 }
309
310 private final class RcsFeatureListener implements FeatureConnector.Listener<RcsFeatureManager> {
311 private FeatureConnector<RcsFeatureManager> mConnector;
312 private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
313 private int mState = STATE_UNAVAILABLE;
314 private int mReason = REASON_IMS_SERVICE_DISCONNECTED;
315 /**
316 * Remember the last return of verifyImsRcsConfigured().
317 * true means ImsResolver found an IMS package for FEATURE_RCS.
318 */
319 private boolean mHasConfig = true;
320
321 private int mSlotId = -1;
322 private String mLogPrefix = "";
323
324 RcsFeatureListener(int slotId) {
325 mLogPrefix = "[RCS, " + slotId + "] ";
326 logv(mLogPrefix + "create");
327
328 mConnector = mRcsFeatureFactory.create(
329 mApp, slotId, this, new HandlerExecutor(mHandler), TAG);
330 mConnector.connect();
331 }
332
333 void setSubId(int subId) {
334 logv(mLogPrefix + "setSubId mSubId=" + mSubId + ", subId=" + subId);
335 if (mSubId == subId) return;
336 logd(mLogPrefix + "setSubId subId changed");
337
338 mSubId = subId;
339 }
340
341 void destroy() {
342 logv(mLogPrefix + "destroy");
343
344 mConnector.disconnect();
345 mConnector = null;
346 }
347
348 @Override
349 public void connectionReady(RcsFeatureManager manager) {
350 logd(mLogPrefix + "connectionReady");
351
352 mState = STATE_READY;
353 mReason = AVAILABLE;
354 mHasConfig = true;
355 onFeatureStateChange(mSubId, FEATURE_RCS, mState, mReason);
356 }
357
358 @Override
359 public void connectionUnavailable(int reason) {
360 logd(mLogPrefix + "connectionUnavailable reason=" + connectorReasonToString(reason));
361
362 reason = convertReasonType(reason);
363 if (mReason == reason) return;
364
365 mState = STATE_UNAVAILABLE;
366 /* If having no IMS package for RCS,
367 * dicard the reason except REASON_NO_IMS_SERVICE_CONFIGURED. */
368 if (!mHasConfig && reason != REASON_NO_IMS_SERVICE_CONFIGURED) return;
369 mReason = reason;
370
371 onFeatureStateChange(mSubId, FEATURE_RCS, mState, mReason);
372 }
373
374 void notifyConfigChanged(boolean hasConfig) {
375 if (mHasConfig == hasConfig) return;
376
377 logd(mLogPrefix + "notifyConfigChanged " + hasConfig);
378
379 mHasConfig = hasConfig;
380 if (hasConfig) {
381 // REASON_NO_IMS_SERVICE_CONFIGURED is already reported to the clients,
382 // since there is no configuration of IMS package for RCS.
383 // Now, a carrier configuration change is notified and
384 // mHasConfig is changed from false to true.
385 // In this case, notify clients the reason, REASON_DISCONNCTED,
386 // to update the state.
387 if (mState != STATE_READY && mReason == REASON_NO_IMS_SERVICE_CONFIGURED) {
388 connectionUnavailable(UNAVAILABLE_REASON_DISCONNECTED);
389 }
390 } else {
391 // FeatureConnector doesn't report UNAVAILABLE_REASON_IMS_UNSUPPORTED,
392 // so report the reason here.
393 connectionUnavailable(UNAVAILABLE_REASON_IMS_UNSUPPORTED);
394 }
395 }
396
397 // called from onRegisterCallback
398 boolean notifyState(CallbackWrapper wrapper) {
399 logv(mLogPrefix + "notifyState subId=" + wrapper.mSubId);
400
401 return wrapper.notifyState(mSubId, FEATURE_RCS, mState, mReason);
402 }
403 }
404
405 /**
406 * A wrapper class for the callback registered
407 */
408 private static class CallbackWrapper {
409 private final int mSubId;
410 private final int mRequiredFeature;
411 private final IImsStateCallback mCallback;
Hunsuk Choi60215822021-11-01 07:19:30 +0000412 private final IBinder mBinder;
Hunsuk Choi3b742d62021-10-25 19:48:34 +0000413
414 CallbackWrapper(int subId, int feature, IImsStateCallback callback) {
415 mSubId = subId;
416 mRequiredFeature = feature;
417 mCallback = callback;
Hunsuk Choi60215822021-11-01 07:19:30 +0000418 mBinder = callback.asBinder();
Hunsuk Choi3b742d62021-10-25 19:48:34 +0000419 }
420
421 /**
422 * @return false when accessing callback binder throws an Exception.
423 * That means the callback binder is not valid any longer.
424 * The death of remote process can cause this.
425 * This instance shall be removed from the list.
426 */
427 boolean notifyState(int subId, int feature, int state, int reason) {
428 logv("CallbackWrapper notifyState subId=" + subId
429 + ", feature=" + ImsFeature.FEATURE_LOG_MAP.get(feature)
430 + ", state=" + ImsFeature.STATE_LOG_MAP.get(state)
431 + ", reason=" + imsStateReasonToString(reason));
432
433 try {
434 if (state == STATE_READY) {
435 mCallback.onAvailable();
436 } else {
437 mCallback.onUnavailable(reason);
438 }
439 } catch (Exception e) {
440 loge("CallbackWrapper notifyState e=" + e);
441 return false;
442 }
443
444 return true;
445 }
446
447 void notifyInactive() {
448 logv("CallbackWrapper notifyInactive subId=" + mSubId);
449
450 try {
451 mCallback.onUnavailable(REASON_SUBSCRIPTION_INACTIVE);
452 } catch (Exception e) {
453 // ignored
454 }
455 }
456 }
457
458 /**
459 * create an instance
460 */
461 public static ImsStateCallbackController make(PhoneGlobals app, int numSlots) {
462 synchronized (ImsStateCallbackController.class) {
463 if (sInstance == null) {
464 logd("ImsStateCallbackController created");
465
466 HandlerThread handlerThread = new HandlerThread(TAG);
467 handlerThread.start();
468 sInstance = new ImsStateCallbackController(app, handlerThread.getLooper(), numSlots,
469 ImsManager::getConnector, RcsFeatureManager::getConnector,
470 ImsResolver.getInstance());
471 }
472 }
473 return sInstance;
474 }
475
476 @VisibleForTesting
477 public ImsStateCallbackController(PhoneGlobals app, Looper looper, int numSlots,
478 MmTelFeatureConnectorFactory mmTelFactory, RcsFeatureConnectorFactory rcsFactory,
479 ImsResolver imsResolver) {
480 mApp = app;
481 mHandler = new MyHandler(looper);
482 mImsResolver = imsResolver;
483 mSubscriptionManager = mApp.getSystemService(SubscriptionManager.class);
484 mTelephonyRegistryManager = mApp.getSystemService(TelephonyRegistryManager.class);
485 mMmTelFeatureFactory = mmTelFactory;
486 mRcsFeatureFactory = rcsFactory;
487
488 updateFeatureControllerSize(numSlots);
489
490 mTelephonyRegistryManager.addOnSubscriptionsChangedListener(
491 mSubChangedListener, mSubChangedListener.getHandlerExecutor());
492
493 mApp.registerReceiver(mReceiver, new IntentFilter(
494 CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
495
496 onSubChanged();
497 }
498
499 /**
500 * Update the number of {@link RcsFeatureController}s that are created based on the number of
501 * active slots on the device.
502 */
503 @VisibleForTesting
504 public void updateFeatureControllerSize(int newNumSlots) {
505 if (mNumSlots != newNumSlots) {
506 Log.d(TAG, "updateFeatures: oldSlots=" + mNumSlots
507 + ", newNumSlots=" + newNumSlots);
508 if (mNumSlots < newNumSlots) {
509 for (int i = mNumSlots; i < newNumSlots; i++) {
510 MmTelFeatureListener m = new MmTelFeatureListener(i);
511 mMmTelFeatureListeners.put(i, m);
512 RcsFeatureListener r = new RcsFeatureListener(i);
513 mRcsFeatureListeners.put(i, r);
514 }
515 } else {
516 for (int i = (mNumSlots - 1); i > (newNumSlots - 1); i--) {
517 MmTelFeatureListener m = mMmTelFeatureListeners.get(i);
518 if (m != null) {
519 mMmTelFeatureListeners.remove(i);
520 m.destroy();
521 }
522 RcsFeatureListener r = mRcsFeatureListeners.get(i);
523 if (r != null) {
524 mRcsFeatureListeners.remove(i);
525 r.destroy();
526 }
527 }
528 }
529 }
530 mNumSlots = newNumSlots;
531 }
532
533 /**
534 * Dependencies for testing.
535 */
536 @VisibleForTesting
537 public void onSubChanged() {
538 logv("onSubChanged size=" + mWrappers.size());
539
540 for (int i = 0; i < mMmTelFeatureListeners.size(); i++) {
541 MmTelFeatureListener l = mMmTelFeatureListeners.valueAt(i);
542 l.setSubId(getSubId(i));
543 }
544
545 for (int i = 0; i < mRcsFeatureListeners.size(); i++) {
546 RcsFeatureListener l = mRcsFeatureListeners.valueAt(i);
547 l.setSubId(getSubId(i));
548 }
549
550 if (mWrappers.size() == 0) return;
551
Hunsuk Choi60215822021-11-01 07:19:30 +0000552 ArrayList<IBinder> inactiveCallbacks = new ArrayList<>();
Hunsuk Choi3b742d62021-10-25 19:48:34 +0000553 final int[] activeSubs = mSubscriptionManager.getActiveSubscriptionIdList();
554
555 logv("onSubChanged activeSubs=" + Arrays.toString(activeSubs));
556
557 // Remove callbacks for inactive subscriptions
Hunsuk Choi60215822021-11-01 07:19:30 +0000558 for (IBinder binder : mWrappers.keySet()) {
559 CallbackWrapper wrapper = mWrappers.get(binder);
Hunsuk Choi3b742d62021-10-25 19:48:34 +0000560 if (wrapper != null) {
561 if (!isActive(activeSubs, wrapper.mSubId)) {
562 // inactive subscription
Hunsuk Choi60215822021-11-01 07:19:30 +0000563 inactiveCallbacks.add(binder);
Hunsuk Choi3b742d62021-10-25 19:48:34 +0000564 }
565 } else {
566 // unexpected, remove it
Hunsuk Choi60215822021-11-01 07:19:30 +0000567 inactiveCallbacks.add(binder);
Hunsuk Choi3b742d62021-10-25 19:48:34 +0000568 }
569 }
570 removeInactiveCallbacks(inactiveCallbacks, "onSubChanged");
571 }
572
573 private void onFeatureStateChange(int subId, int feature, int state, int reason) {
574 logv("onFeatureStateChange subId=" + subId
575 + ", feature=" + ImsFeature.FEATURE_LOG_MAP.get(feature)
576 + ", state=" + ImsFeature.STATE_LOG_MAP.get(state)
577 + ", reason=" + imsStateReasonToString(reason));
578
Hunsuk Choi60215822021-11-01 07:19:30 +0000579 ArrayList<IBinder> inactiveCallbacks = new ArrayList<>();
Hunsuk Choi3b742d62021-10-25 19:48:34 +0000580 mWrappers.values().forEach(wrapper -> {
581 if (subId == wrapper.mSubId
582 && feature == wrapper.mRequiredFeature
583 && !wrapper.notifyState(subId, feature, state, reason)) {
584 // callback has exception, remove it
Hunsuk Choi60215822021-11-01 07:19:30 +0000585 inactiveCallbacks.add(wrapper.mBinder);
Hunsuk Choi3b742d62021-10-25 19:48:34 +0000586 }
587 });
588 removeInactiveCallbacks(inactiveCallbacks, "onFeatureStateChange");
589 }
590
591 private void onRegisterCallback(CallbackWrapper wrapper) {
592 if (wrapper == null) return;
593
594 logv("onRegisterCallback before size=" + mWrappers.size());
595 logv("onRegisterCallback subId=" + wrapper.mSubId
596 + ", feature=" + wrapper.mRequiredFeature);
597
598 // Not sure the following case can happen or not:
599 // step1) Subscription changed
600 // step2) ImsStateCallbackController not processed onSubChanged yet
601 // step3) Client registers with a strange subId
602 // The validity of the subId is checked PhoneInterfaceManager#registerImsStateCallback.
603 // So, register the wrapper here before trying to notifyState.
604 // TODO: implement the recovery for this case, notifying the current reson, in onSubChanged
Hunsuk Choi60215822021-11-01 07:19:30 +0000605 mWrappers.put(wrapper.mBinder, wrapper);
Hunsuk Choi3b742d62021-10-25 19:48:34 +0000606
607 if (wrapper.mRequiredFeature == FEATURE_MMTEL) {
608 for (int i = 0; i < mMmTelFeatureListeners.size(); i++) {
609 MmTelFeatureListener l = mMmTelFeatureListeners.valueAt(i);
610 if (l.mSubId == wrapper.mSubId
611 && !l.notifyState(wrapper)) {
Hunsuk Choi60215822021-11-01 07:19:30 +0000612 mWrappers.remove(wrapper.mBinder);
Hunsuk Choi3b742d62021-10-25 19:48:34 +0000613 break;
614 }
615 }
616 } else if (wrapper.mRequiredFeature == FEATURE_RCS) {
617 for (int i = 0; i < mRcsFeatureListeners.size(); i++) {
618 RcsFeatureListener l = mRcsFeatureListeners.valueAt(i);
619 if (l.mSubId == wrapper.mSubId
620 && !l.notifyState(wrapper)) {
Hunsuk Choi60215822021-11-01 07:19:30 +0000621 mWrappers.remove(wrapper.mBinder);
Hunsuk Choi3b742d62021-10-25 19:48:34 +0000622 break;
623 }
624 }
625 }
626
627 logv("onRegisterCallback after size=" + mWrappers.size());
628 }
629
630 private void onUnregisterCallback(IImsStateCallback cb) {
631 if (cb == null) return;
Hunsuk Choi60215822021-11-01 07:19:30 +0000632 mWrappers.remove(cb.asBinder());
Hunsuk Choi3b742d62021-10-25 19:48:34 +0000633 }
634
635 private void onCarrierConfigChanged(int slotId) {
636 if (slotId >= mNumSlots) {
637 logd("onCarrierConfigChanged invalid slotId "
638 + slotId + ", mNumSlots=" + mNumSlots);
639 return;
640 }
641
642 logd("onCarrierConfigChanged slotId=" + slotId);
643
644 boolean hasConfig = verifyImsMmTelConfigured(slotId);
645 if (slotId < mMmTelFeatureListeners.size()) {
646 MmTelFeatureListener listener = mMmTelFeatureListeners.valueAt(slotId);
647 listener.notifyConfigChanged(hasConfig);
648 }
649
650 hasConfig = verifyImsRcsConfigured(slotId);
651 if (slotId < mRcsFeatureListeners.size()) {
652 RcsFeatureListener listener = mRcsFeatureListeners.valueAt(slotId);
653 listener.notifyConfigChanged(hasConfig);
654 }
655 }
656
657 /**
658 * Notifies carrier configuration has changed.
659 */
660 @VisibleForTesting
661 public void notifyCarrierConfigChanged(int slotId) {
662 logv("notifyCarrierConfigChanged slotId=" + slotId);
663 mHandler.sendMessage(mHandler.obtainMessage(EVENT_CARRIER_CONFIG_CHANGED, slotId, 0));
664 }
665 /**
666 * Register IImsStateCallback
667 *
668 * @param feature for which state is changed, ImsFeature.FEATURE_*
669 */
670 public void registerImsStateCallback(int subId, int feature, IImsStateCallback cb) {
671 logv("registerImsStateCallback subId=" + subId + ", feature=" + feature);
672
673 CallbackWrapper wrapper = new CallbackWrapper(subId, feature, cb);
674 mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_CALLBACK, wrapper));
675 }
676
677 /**
678 * Unegister previously registered callback
679 */
680 public void unregisterImsStateCallback(IImsStateCallback cb) {
681 logv("unregisterImsStateCallback");
682
683 mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_CALLBACK, cb));
684 }
685
686 private void removeInactiveCallbacks(
Hunsuk Choi60215822021-11-01 07:19:30 +0000687 ArrayList<IBinder> inactiveCallbacks, String message) {
Hunsuk Choi3b742d62021-10-25 19:48:34 +0000688 if (inactiveCallbacks == null || inactiveCallbacks.size() == 0) return;
689
690 logv("removeInactiveCallbacks size=" + inactiveCallbacks.size() + " from " + message);
691
Hunsuk Choi60215822021-11-01 07:19:30 +0000692 for (IBinder binder : inactiveCallbacks) {
693 CallbackWrapper wrapper = mWrappers.get(binder);
Hunsuk Choi3b742d62021-10-25 19:48:34 +0000694 if (wrapper != null) {
695 // Send the reason REASON_SUBSCRIPTION_INACTIVE to the client
696 wrapper.notifyInactive();
Hunsuk Choi60215822021-11-01 07:19:30 +0000697 mWrappers.remove(binder);
Hunsuk Choi3b742d62021-10-25 19:48:34 +0000698 }
699 }
700 inactiveCallbacks.clear();
701 }
702
703 private int getSubId(int slotId) {
704 Phone phone = mPhoneFactoryProxy.getPhone(slotId);
705 if (phone != null) return phone.getSubId();
706 return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
707 }
708
709 private static boolean isActive(final int[] activeSubs, int subId) {
710 for (int i : activeSubs) {
711 if (i == subId) return true;
712 }
713 return false;
714 }
715
716 private static int convertReasonType(int reason) {
717 switch(reason) {
718 case UNAVAILABLE_REASON_NOT_READY:
719 return REASON_IMS_SERVICE_NOT_READY;
720 case UNAVAILABLE_REASON_IMS_UNSUPPORTED:
721 return REASON_NO_IMS_SERVICE_CONFIGURED;
722 default:
723 break;
724 }
725
726 return REASON_IMS_SERVICE_DISCONNECTED;
727 }
728
729 private boolean verifyImsMmTelConfigured(int slotId) {
730 boolean ret = false;
731 if (mImsResolver == null) {
732 loge("verifyImsMmTelConfigured mImsResolver is null");
733 } else {
734 ret = mImsResolver.isImsServiceConfiguredForFeature(slotId, FEATURE_MMTEL);
735 }
736 logv("verifyImsMmTelConfigured slotId=" + slotId + ", ret=" + ret);
737 return ret;
738 }
739
740 private boolean verifyImsRcsConfigured(int slotId) {
741 boolean ret = false;
742 if (mImsResolver == null) {
743 loge("verifyImsRcsConfigured mImsResolver is null");
744 } else {
745 ret = mImsResolver.isImsServiceConfiguredForFeature(slotId, FEATURE_RCS);
746 }
747 logv("verifyImsRcsConfigured slotId=" + slotId + ", ret=" + ret);
748 return ret;
749 }
750
751 private static String connectorReasonToString(int reason) {
752 switch(reason) {
753 case UNAVAILABLE_REASON_DISCONNECTED:
754 return "DISCONNECTED";
755 case UNAVAILABLE_REASON_NOT_READY:
756 return "NOT_READY";
757 case UNAVAILABLE_REASON_IMS_UNSUPPORTED:
758 return "IMS_UNSUPPORTED";
759 case UNAVAILABLE_REASON_SERVER_UNAVAILABLE:
760 return "SERVER_UNAVAILABLE";
761 default:
762 break;
763 }
764 return "";
765 }
766
767 private static String imsStateReasonToString(int reason) {
768 switch(reason) {
769 case REASON_UNKNOWN_TEMPORARY_ERROR:
770 return "UNKNOWN_TEMPORARY_ERROR";
771 case REASON_UNKNOWN_PERMANENT_ERROR:
772 return "UNKNOWN_PERMANENT_ERROR";
773 case REASON_IMS_SERVICE_DISCONNECTED:
774 return "IMS_SERVICE_DISCONNECTED";
775 case REASON_NO_IMS_SERVICE_CONFIGURED:
776 return "NO_IMS_SERVICE_CONFIGURED";
777 case REASON_SUBSCRIPTION_INACTIVE:
778 return "SUBSCRIPTION_INACTIVE";
779 case REASON_IMS_SERVICE_NOT_READY:
780 return "IMS_SERVICE_NOT_READY";
781 default:
782 break;
783 }
784 return "";
785 }
786
787 /**
788 * PhoneFactory Dependencies for testing.
789 */
790 @VisibleForTesting
791 public interface PhoneFactoryProxy {
792 /**
793 * Override getPhone for testing.
794 */
795 Phone getPhone(int index);
796 }
797
798 private PhoneFactoryProxy mPhoneFactoryProxy = new PhoneFactoryProxy() {
799 @Override
800 public Phone getPhone(int index) {
801 return PhoneFactory.getPhone(index);
802 }
803 };
804
805 private void release() {
806 logv("release");
807
808 mTelephonyRegistryManager.removeOnSubscriptionsChangedListener(mSubChangedListener);
809 mApp.unregisterReceiver(mReceiver);
810
811 for (int i = 0; i < mMmTelFeatureListeners.size(); i++) {
812 mMmTelFeatureListeners.valueAt(i).destroy();
813 }
814 mMmTelFeatureListeners.clear();
815
816 for (int i = 0; i < mRcsFeatureListeners.size(); i++) {
817 mRcsFeatureListeners.valueAt(i).destroy();
818 }
819 mRcsFeatureListeners.clear();
820 }
821
822 /**
823 * destroy the instance
824 */
825 @VisibleForTesting
826 public void destroy() {
827 logv("destroy it");
828
829 release();
830 mHandler.getLooper().quit();
831 }
832
833 /**
834 * get the handler
835 */
836 @VisibleForTesting
837 public Handler getHandler() {
838 return mHandler;
839 }
840
841 /**
842 * Determine whether the callback is registered or not
843 */
844 @VisibleForTesting
845 public boolean isRegistered(IImsStateCallback cb) {
846 if (cb == null) return false;
Hunsuk Choi60215822021-11-01 07:19:30 +0000847 return mWrappers.containsKey(cb.asBinder());
Hunsuk Choi3b742d62021-10-25 19:48:34 +0000848 }
849
850 private static void logv(String msg) {
851 if (VDBG) {
852 Rlog.d(TAG, msg);
853 }
854 }
855
856 private static void logd(String msg) {
857 Rlog.d(TAG, msg);
858 }
859
860 private static void loge(String msg) {
861 Rlog.e(TAG, msg);
862 }
863}