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