Loading src/java/com/android/internal/telephony/Phone.java +13 −6 Original line number Diff line number Diff line Loading @@ -58,6 +58,7 @@ import com.android.ims.ImsConfig; import com.android.ims.ImsManager; import com.android.internal.R; import com.android.internal.telephony.dataconnection.DcTracker; import com.android.internal.telephony.ims.ImsResolver; import com.android.internal.telephony.test.SimulatedRadioControl; import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType; import com.android.internal.telephony.uicc.IccFileHandler; Loading Loading @@ -522,7 +523,8 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { } /** * Start listening for IMS service UP/DOWN events. * Start listening for IMS service UP/DOWN events. If using the new ImsResolver APIs, we should * always be setting up ImsPhones. */ public void startMonitoringImsService() { if (getPhoneType() == PhoneConstants.PHONE_TYPE_SIP) { Loading @@ -537,15 +539,20 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { mContext.registerReceiver(mImsIntentReceiver, filter); // Monitor IMS service - but first poll to see if already up (could miss // intent) // intent). Also, when using new ImsResolver APIs, the service will be available soon, // so start trying to bind. ImsManager imsManager = ImsManager.getInstance(mContext, getPhoneId()); if (imsManager != null && imsManager.isServiceAvailable()) { if (imsManager != null) { // If it is dynamic binding, kick off ImsPhone creation now instead of waiting for // the service to be available. if (imsManager.isDynamicBinding() || imsManager.isServiceAvailable()) { mImsServiceReady = true; updateImsPhone(); ImsManager.updateImsServiceConfig(mContext, mPhoneId, false); } } } } /** * When overridden the derived class needs to call Loading src/java/com/android/internal/telephony/PhoneFactory.java +10 −7 Original line number Diff line number Diff line Loading @@ -138,6 +138,13 @@ public class PhoneFactory { where as in single SIM mode only instance. isMultiSimEnabled() function checks whether it is single SIM or multi SIM mode */ int numPhones = TelephonyManager.getDefault().getPhoneCount(); // Start ImsResolver and bind to ImsServices. String defaultImsPackage = sContext.getResources().getString( com.android.internal.R.string.config_ims_package); Rlog.i(LOG_TAG, "ImsResolver: defaultImsPackage: " + defaultImsPackage); sImsResolver = new ImsResolver(sContext, defaultImsPackage, numPhones); sImsResolver.populateCacheAndStartBind(); int[] networkModes = new int[numPhones]; sPhones = new Phone[numPhones]; sCommandsInterfaces = new RIL[numPhones]; Loading Loading @@ -205,8 +212,9 @@ public class PhoneFactory { SubscriptionController.getInstance().updatePhonesAvailability(sPhones); // Start monitoring after defaults have been made. // Default phone must be ready before ImsPhone is created // because ImsService might need it when it is being opened. // Default phone must be ready before ImsPhone is created because ImsService might // need it when it is being opened. This should initialize multiple ImsPhones for // ImsResolver implementations of ImsService. for (int i = 0; i < numPhones; i++) { sPhones[i].startMonitoringImsService(); } Loading @@ -230,11 +238,6 @@ public class PhoneFactory { sPhoneSwitcher, sc, sSubscriptionMonitor, Looper.myLooper(), sContext, i, sPhones[i].mDcTracker); } String defaultImsPackage = sContext.getResources().getString( com.android.internal.R.string.config_ims_package); Rlog.i(LOG_TAG, "ImsResolver: defaultImsPackage: " + defaultImsPackage); sImsResolver = new ImsResolver(sContext, defaultImsPackage, numPhones); sImsResolver.populateCacheAndStartBind(); } } } Loading src/java/com/android/internal/telephony/ims/ImsServiceController.java +58 −1 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import android.os.ServiceManager; import android.util.Log; import android.util.Pair; import com.android.ims.internal.IImsFeatureStatusCallback; import com.android.ims.internal.IImsServiceController; import com.android.ims.internal.IImsServiceFeatureListener; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -164,6 +165,36 @@ public class ImsServiceController { private ImsServiceConnection mImsServiceConnection; private ImsDeathRecipient mImsDeathRecipient; private Set<IImsServiceFeatureListener> mImsStatusCallbacks = new HashSet<>(); // Only added or removed, never accessed on purpose. private Set<ImsFeatureStatusCallback> mFeatureStatusCallbacks = new HashSet<>(); /** * Container class for the IImsFeatureStatusCallback callback implementation. This class is * never used directly, but we need to keep track of the IImsFeatureStatusCallback * implementations explicitly. */ private class ImsFeatureStatusCallback { private int mSlotId; private int mFeatureType; private final IImsFeatureStatusCallback mCallback = new IImsFeatureStatusCallback.Stub() { @Override public void notifyImsFeatureStatus(int featureStatus) throws RemoteException { Log.i(LOG_TAG, "notifyImsFeatureStatus"); sendImsFeatureStatusChanged(mSlotId, mFeatureType, featureStatus); } }; ImsFeatureStatusCallback(int slotId, int featureType) { mSlotId = slotId; mFeatureType = featureType; } public IImsFeatureStatusCallback getCallback() { return mCallback; } } // Retry the bind to the ImsService that has died after mRebindRetry timeout. private Runnable mRestartImsServiceRunnable = new Runnable() { Loading Loading @@ -376,18 +407,41 @@ public class ImsServiceController { } } private void sendImsFeatureStatusChanged(int slot, int feature, int status) { synchronized (mLock) { for (Iterator<IImsServiceFeatureListener> i = mImsStatusCallbacks.iterator(); i.hasNext(); ) { IImsServiceFeatureListener callbacks = i.next(); try { callbacks.imsStatusChanged(slot, feature, status); } catch (RemoteException e) { // binder died, remove callback. Log.w(LOG_TAG, "sendImsFeatureStatusChanged: Binder died, removing " + "callback. Exception:" + e.getMessage()); i.remove(); } } } } // This method should only be called when synchronized on mLock private void addImsServiceFeature(Pair<Integer, Integer> featurePair) throws RemoteException { if (mIImsServiceController == null || mCallbacks == null) { Log.w(LOG_TAG, "addImsServiceFeature called with null values."); return; } mIImsServiceController.createImsFeature(featurePair.first, featurePair.second); ImsFeatureStatusCallback c = new ImsFeatureStatusCallback(featurePair.first, featurePair.second); mFeatureStatusCallbacks.add(c); mIImsServiceController.createImsFeature(featurePair.first, featurePair.second, c.getCallback()); // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController mCallbacks.imsServiceFeatureCreated(featurePair.first, featurePair.second, this); // Send callback to ImsServiceProxy to change supported ImsFeatures sendImsFeatureCreatedCallback(featurePair.first, featurePair.second); } // This method should only be called when synchronized on mLock private void removeImsServiceFeature(Pair<Integer, Integer> featurePair) throws RemoteException { if (mIImsServiceController == null || mCallbacks == null) { Loading @@ -402,6 +456,9 @@ public class ImsServiceController { // ImsManager requests the ImsService while it is being removed in ImsResolver, this // callback will clean it up after. sendImsFeatureRemovedCallback(featurePair.first, featurePair.second); // Remove status callbacks from list. mFeatureStatusCallbacks.removeIf(c -> c.mSlotId == featurePair.first && c.mFeatureType == featurePair.second); } private void notifyAllFeaturesRemoved() { Loading src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java +41 −18 Original line number Diff line number Diff line Loading @@ -45,6 +45,8 @@ import android.telephony.Rlog; import android.telephony.ServiceState; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.ims.ImsServiceProxy; import android.telephony.ims.feature.ImsFeature; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; Loading Loading @@ -220,10 +222,12 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { // connection due to the modem not responding. private static final int TIMEOUT_CLEAR_DISCONNECTING_CONN = 5000; // The number of times we will try to connect to the ImsService before giving up. private static final int NUM_IMS_SERVICE_RETRIES = 10; // The number of milliseconds in between each try. private static final int TIME_BETWEEN_IMS_SERVICE_RETRIES_MS = 400; // ms // Initial condition for ims connection retry. private static final int IMS_RETRY_STARTING_TIMEOUT_MS = 500; // ms // Ceiling bitshift amount for service query timeout, calculated as: // 2^mImsServiceRetryCount * IMS_RETRY_STARTING_TIMEOUT_MS, where // mImsServiceRetryCount ∊ [0, CEILING_SERVICE_RETRY_COUNT]. private static final int CEILING_SERVICE_RETRY_COUNT = 6; private static final int HANDOVER_TO_WIFI_TIMEOUT_MS = 60000; // ms Loading Loading @@ -328,6 +332,17 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { */ private Map<Pair<Integer, String>, Integer> mImsReasonCodeMap = new ArrayMap<>(); private ImsServiceProxy.INotifyStatusChanged mNotifyFeatureRemovedCallback = () -> { try { if (mImsManager.getImsServiceStatus() != ImsFeature.STATE_READY) { retryGetImsService(); } } catch (ImsException e) { // Could not get the ImsService, retry! retryGetImsService(); } }; //***** Events Loading Loading @@ -363,6 +378,14 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { private void getImsService() throws ImsException { if (DBG) log("getImsService"); mImsManager = ImsManager.getInstance(mPhone.getContext(), mPhone.getPhoneId()); // Adding to set, will be safe adding multiple times. mImsManager.addNotifyStatusChangedCallback(mNotifyFeatureRemovedCallback); if (mImsManager.getImsServiceStatus() != ImsFeature.STATE_READY) { // We can not call "open" until the ims service is ready throw new ImsException("getImsServiceStatus()", ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); } mImsServiceRetryCount = 0; mServiceId = mImsManager.open(ImsServiceClass.MMTEL, createIncomingCallPendingIntent(), mImsConnectionStateListener); Loading Loading @@ -2398,20 +2421,7 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { getImsService(); } catch (ImsException e) { loge("getImsService: " + e); //Leave mImsManager as null, then CallStateException will be thrown when dialing mImsManager = null; if (mImsServiceRetryCount < NUM_IMS_SERVICE_RETRIES) { loge("getImsService: Retrying getting ImsService..."); sendEmptyMessageDelayed(EVENT_GET_IMS_SERVICE, TIME_BETWEEN_IMS_SERVICE_RETRIES_MS); mImsServiceRetryCount++; } else { // We have been unable to connect for // NUM_IMS_SERVICE_RETRIES*TIME_BETWEEN_IMS_SERVICE_RETRIES_MS ms. We will // probably never be able to connect, so we should just give up. loge("getImsService: ImsService retrieval timeout... ImsService is " + "unavailable."); } retryGetImsService(); } break; case EVENT_CHECK_FOR_WIFI_HANDOVER: Loading Loading @@ -2576,6 +2586,19 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { return mState; } private void retryGetImsService() { //Leave mImsManager as null, then CallStateException will be thrown when dialing mImsManager = null; // Exponential backoff during retry, limited to 32 seconds. loge("getImsService: Retrying getting ImsService..."); removeMessages(EVENT_GET_IMS_SERVICE); sendEmptyMessageDelayed(EVENT_GET_IMS_SERVICE, (1 << mImsServiceRetryCount) * IMS_RETRY_STARTING_TIMEOUT_MS); if (mImsServiceRetryCount <= CEILING_SERVICE_RETRY_COUNT) { mImsServiceRetryCount++; } } private void setVideoCallProvider(ImsPhoneConnection conn, ImsCall imsCall) throws RemoteException { IImsVideoCallProvider imsVideoCallProvider = Loading tests/telephonytests/src/android/telephony/ims/ImsFeatureTest.java 0 → 100644 +114 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.telephony.ims; import android.support.test.runner.AndroidJUnit4; import android.telephony.ims.feature.ImsFeature; import android.test.suitebuilder.annotation.SmallTest; import com.android.ims.internal.IImsFeatureStatusCallback; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @RunWith(AndroidJUnit4.class) public class ImsFeatureTest { private TestImsFeature mTestImsService; @Mock private IImsFeatureStatusCallback mTestStatusCallback; @Mock private ImsFeature.INotifyFeatureRemoved mTestRemovedCallback; private class TestImsFeature extends ImsFeature { public boolean featureRemovedCalled = false; @Override public void onFeatureRemoved() { featureRemovedCalled = true; } public void testSetFeatureState(int featureState) { setFeatureState(featureState); } } @Before public void setUp() { MockitoAnnotations.initMocks(this); mTestImsService = new TestImsFeature(); } @After public void tearDown() { mTestImsService = null; } @Test @SmallTest public void testSetCallbackAndNotify() throws Exception { mTestImsService.setImsFeatureStatusCallback(mTestStatusCallback); verify(mTestStatusCallback).notifyImsFeatureStatus(eq(ImsFeature.STATE_NOT_AVAILABLE)); } @Test @SmallTest public void testSetFeatureAndCheckCallback() throws Exception { mTestImsService.setImsFeatureStatusCallback(mTestStatusCallback); mTestImsService.testSetFeatureState(ImsFeature.STATE_READY); verify(mTestStatusCallback).notifyImsFeatureStatus(eq(ImsFeature.STATE_READY)); assertEquals(ImsFeature.STATE_READY, mTestImsService.getFeatureState()); } @Test @SmallTest public void testRegisterAndNotifyRemoveFeature() { mTestImsService.addFeatureRemovedListener(mTestRemovedCallback); mTestImsService.notifyFeatureRemoved(0); verify(mTestRemovedCallback).onFeatureRemoved(eq(0)); assertTrue(mTestImsService.featureRemovedCalled); } @Test @SmallTest public void testRegisterAndUnregisterNotify() { mTestImsService.addFeatureRemovedListener(mTestRemovedCallback); mTestImsService.removeFeatureRemovedListener(mTestRemovedCallback); mTestImsService.notifyFeatureRemoved(0); verify(mTestRemovedCallback, never()).onFeatureRemoved(eq(0)); assertTrue(mTestImsService.featureRemovedCalled); } } Loading
src/java/com/android/internal/telephony/Phone.java +13 −6 Original line number Diff line number Diff line Loading @@ -58,6 +58,7 @@ import com.android.ims.ImsConfig; import com.android.ims.ImsManager; import com.android.internal.R; import com.android.internal.telephony.dataconnection.DcTracker; import com.android.internal.telephony.ims.ImsResolver; import com.android.internal.telephony.test.SimulatedRadioControl; import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType; import com.android.internal.telephony.uicc.IccFileHandler; Loading Loading @@ -522,7 +523,8 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { } /** * Start listening for IMS service UP/DOWN events. * Start listening for IMS service UP/DOWN events. If using the new ImsResolver APIs, we should * always be setting up ImsPhones. */ public void startMonitoringImsService() { if (getPhoneType() == PhoneConstants.PHONE_TYPE_SIP) { Loading @@ -537,15 +539,20 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { mContext.registerReceiver(mImsIntentReceiver, filter); // Monitor IMS service - but first poll to see if already up (could miss // intent) // intent). Also, when using new ImsResolver APIs, the service will be available soon, // so start trying to bind. ImsManager imsManager = ImsManager.getInstance(mContext, getPhoneId()); if (imsManager != null && imsManager.isServiceAvailable()) { if (imsManager != null) { // If it is dynamic binding, kick off ImsPhone creation now instead of waiting for // the service to be available. if (imsManager.isDynamicBinding() || imsManager.isServiceAvailable()) { mImsServiceReady = true; updateImsPhone(); ImsManager.updateImsServiceConfig(mContext, mPhoneId, false); } } } } /** * When overridden the derived class needs to call Loading
src/java/com/android/internal/telephony/PhoneFactory.java +10 −7 Original line number Diff line number Diff line Loading @@ -138,6 +138,13 @@ public class PhoneFactory { where as in single SIM mode only instance. isMultiSimEnabled() function checks whether it is single SIM or multi SIM mode */ int numPhones = TelephonyManager.getDefault().getPhoneCount(); // Start ImsResolver and bind to ImsServices. String defaultImsPackage = sContext.getResources().getString( com.android.internal.R.string.config_ims_package); Rlog.i(LOG_TAG, "ImsResolver: defaultImsPackage: " + defaultImsPackage); sImsResolver = new ImsResolver(sContext, defaultImsPackage, numPhones); sImsResolver.populateCacheAndStartBind(); int[] networkModes = new int[numPhones]; sPhones = new Phone[numPhones]; sCommandsInterfaces = new RIL[numPhones]; Loading Loading @@ -205,8 +212,9 @@ public class PhoneFactory { SubscriptionController.getInstance().updatePhonesAvailability(sPhones); // Start monitoring after defaults have been made. // Default phone must be ready before ImsPhone is created // because ImsService might need it when it is being opened. // Default phone must be ready before ImsPhone is created because ImsService might // need it when it is being opened. This should initialize multiple ImsPhones for // ImsResolver implementations of ImsService. for (int i = 0; i < numPhones; i++) { sPhones[i].startMonitoringImsService(); } Loading @@ -230,11 +238,6 @@ public class PhoneFactory { sPhoneSwitcher, sc, sSubscriptionMonitor, Looper.myLooper(), sContext, i, sPhones[i].mDcTracker); } String defaultImsPackage = sContext.getResources().getString( com.android.internal.R.string.config_ims_package); Rlog.i(LOG_TAG, "ImsResolver: defaultImsPackage: " + defaultImsPackage); sImsResolver = new ImsResolver(sContext, defaultImsPackage, numPhones); sImsResolver.populateCacheAndStartBind(); } } } Loading
src/java/com/android/internal/telephony/ims/ImsServiceController.java +58 −1 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import android.os.ServiceManager; import android.util.Log; import android.util.Pair; import com.android.ims.internal.IImsFeatureStatusCallback; import com.android.ims.internal.IImsServiceController; import com.android.ims.internal.IImsServiceFeatureListener; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -164,6 +165,36 @@ public class ImsServiceController { private ImsServiceConnection mImsServiceConnection; private ImsDeathRecipient mImsDeathRecipient; private Set<IImsServiceFeatureListener> mImsStatusCallbacks = new HashSet<>(); // Only added or removed, never accessed on purpose. private Set<ImsFeatureStatusCallback> mFeatureStatusCallbacks = new HashSet<>(); /** * Container class for the IImsFeatureStatusCallback callback implementation. This class is * never used directly, but we need to keep track of the IImsFeatureStatusCallback * implementations explicitly. */ private class ImsFeatureStatusCallback { private int mSlotId; private int mFeatureType; private final IImsFeatureStatusCallback mCallback = new IImsFeatureStatusCallback.Stub() { @Override public void notifyImsFeatureStatus(int featureStatus) throws RemoteException { Log.i(LOG_TAG, "notifyImsFeatureStatus"); sendImsFeatureStatusChanged(mSlotId, mFeatureType, featureStatus); } }; ImsFeatureStatusCallback(int slotId, int featureType) { mSlotId = slotId; mFeatureType = featureType; } public IImsFeatureStatusCallback getCallback() { return mCallback; } } // Retry the bind to the ImsService that has died after mRebindRetry timeout. private Runnable mRestartImsServiceRunnable = new Runnable() { Loading Loading @@ -376,18 +407,41 @@ public class ImsServiceController { } } private void sendImsFeatureStatusChanged(int slot, int feature, int status) { synchronized (mLock) { for (Iterator<IImsServiceFeatureListener> i = mImsStatusCallbacks.iterator(); i.hasNext(); ) { IImsServiceFeatureListener callbacks = i.next(); try { callbacks.imsStatusChanged(slot, feature, status); } catch (RemoteException e) { // binder died, remove callback. Log.w(LOG_TAG, "sendImsFeatureStatusChanged: Binder died, removing " + "callback. Exception:" + e.getMessage()); i.remove(); } } } } // This method should only be called when synchronized on mLock private void addImsServiceFeature(Pair<Integer, Integer> featurePair) throws RemoteException { if (mIImsServiceController == null || mCallbacks == null) { Log.w(LOG_TAG, "addImsServiceFeature called with null values."); return; } mIImsServiceController.createImsFeature(featurePair.first, featurePair.second); ImsFeatureStatusCallback c = new ImsFeatureStatusCallback(featurePair.first, featurePair.second); mFeatureStatusCallbacks.add(c); mIImsServiceController.createImsFeature(featurePair.first, featurePair.second, c.getCallback()); // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController mCallbacks.imsServiceFeatureCreated(featurePair.first, featurePair.second, this); // Send callback to ImsServiceProxy to change supported ImsFeatures sendImsFeatureCreatedCallback(featurePair.first, featurePair.second); } // This method should only be called when synchronized on mLock private void removeImsServiceFeature(Pair<Integer, Integer> featurePair) throws RemoteException { if (mIImsServiceController == null || mCallbacks == null) { Loading @@ -402,6 +456,9 @@ public class ImsServiceController { // ImsManager requests the ImsService while it is being removed in ImsResolver, this // callback will clean it up after. sendImsFeatureRemovedCallback(featurePair.first, featurePair.second); // Remove status callbacks from list. mFeatureStatusCallbacks.removeIf(c -> c.mSlotId == featurePair.first && c.mFeatureType == featurePair.second); } private void notifyAllFeaturesRemoved() { Loading
src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java +41 −18 Original line number Diff line number Diff line Loading @@ -45,6 +45,8 @@ import android.telephony.Rlog; import android.telephony.ServiceState; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.ims.ImsServiceProxy; import android.telephony.ims.feature.ImsFeature; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; Loading Loading @@ -220,10 +222,12 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { // connection due to the modem not responding. private static final int TIMEOUT_CLEAR_DISCONNECTING_CONN = 5000; // The number of times we will try to connect to the ImsService before giving up. private static final int NUM_IMS_SERVICE_RETRIES = 10; // The number of milliseconds in between each try. private static final int TIME_BETWEEN_IMS_SERVICE_RETRIES_MS = 400; // ms // Initial condition for ims connection retry. private static final int IMS_RETRY_STARTING_TIMEOUT_MS = 500; // ms // Ceiling bitshift amount for service query timeout, calculated as: // 2^mImsServiceRetryCount * IMS_RETRY_STARTING_TIMEOUT_MS, where // mImsServiceRetryCount ∊ [0, CEILING_SERVICE_RETRY_COUNT]. private static final int CEILING_SERVICE_RETRY_COUNT = 6; private static final int HANDOVER_TO_WIFI_TIMEOUT_MS = 60000; // ms Loading Loading @@ -328,6 +332,17 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { */ private Map<Pair<Integer, String>, Integer> mImsReasonCodeMap = new ArrayMap<>(); private ImsServiceProxy.INotifyStatusChanged mNotifyFeatureRemovedCallback = () -> { try { if (mImsManager.getImsServiceStatus() != ImsFeature.STATE_READY) { retryGetImsService(); } } catch (ImsException e) { // Could not get the ImsService, retry! retryGetImsService(); } }; //***** Events Loading Loading @@ -363,6 +378,14 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { private void getImsService() throws ImsException { if (DBG) log("getImsService"); mImsManager = ImsManager.getInstance(mPhone.getContext(), mPhone.getPhoneId()); // Adding to set, will be safe adding multiple times. mImsManager.addNotifyStatusChangedCallback(mNotifyFeatureRemovedCallback); if (mImsManager.getImsServiceStatus() != ImsFeature.STATE_READY) { // We can not call "open" until the ims service is ready throw new ImsException("getImsServiceStatus()", ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN); } mImsServiceRetryCount = 0; mServiceId = mImsManager.open(ImsServiceClass.MMTEL, createIncomingCallPendingIntent(), mImsConnectionStateListener); Loading Loading @@ -2398,20 +2421,7 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { getImsService(); } catch (ImsException e) { loge("getImsService: " + e); //Leave mImsManager as null, then CallStateException will be thrown when dialing mImsManager = null; if (mImsServiceRetryCount < NUM_IMS_SERVICE_RETRIES) { loge("getImsService: Retrying getting ImsService..."); sendEmptyMessageDelayed(EVENT_GET_IMS_SERVICE, TIME_BETWEEN_IMS_SERVICE_RETRIES_MS); mImsServiceRetryCount++; } else { // We have been unable to connect for // NUM_IMS_SERVICE_RETRIES*TIME_BETWEEN_IMS_SERVICE_RETRIES_MS ms. We will // probably never be able to connect, so we should just give up. loge("getImsService: ImsService retrieval timeout... ImsService is " + "unavailable."); } retryGetImsService(); } break; case EVENT_CHECK_FOR_WIFI_HANDOVER: Loading Loading @@ -2576,6 +2586,19 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall { return mState; } private void retryGetImsService() { //Leave mImsManager as null, then CallStateException will be thrown when dialing mImsManager = null; // Exponential backoff during retry, limited to 32 seconds. loge("getImsService: Retrying getting ImsService..."); removeMessages(EVENT_GET_IMS_SERVICE); sendEmptyMessageDelayed(EVENT_GET_IMS_SERVICE, (1 << mImsServiceRetryCount) * IMS_RETRY_STARTING_TIMEOUT_MS); if (mImsServiceRetryCount <= CEILING_SERVICE_RETRY_COUNT) { mImsServiceRetryCount++; } } private void setVideoCallProvider(ImsPhoneConnection conn, ImsCall imsCall) throws RemoteException { IImsVideoCallProvider imsVideoCallProvider = Loading
tests/telephonytests/src/android/telephony/ims/ImsFeatureTest.java 0 → 100644 +114 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.telephony.ims; import android.support.test.runner.AndroidJUnit4; import android.telephony.ims.feature.ImsFeature; import android.test.suitebuilder.annotation.SmallTest; import com.android.ims.internal.IImsFeatureStatusCallback; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @RunWith(AndroidJUnit4.class) public class ImsFeatureTest { private TestImsFeature mTestImsService; @Mock private IImsFeatureStatusCallback mTestStatusCallback; @Mock private ImsFeature.INotifyFeatureRemoved mTestRemovedCallback; private class TestImsFeature extends ImsFeature { public boolean featureRemovedCalled = false; @Override public void onFeatureRemoved() { featureRemovedCalled = true; } public void testSetFeatureState(int featureState) { setFeatureState(featureState); } } @Before public void setUp() { MockitoAnnotations.initMocks(this); mTestImsService = new TestImsFeature(); } @After public void tearDown() { mTestImsService = null; } @Test @SmallTest public void testSetCallbackAndNotify() throws Exception { mTestImsService.setImsFeatureStatusCallback(mTestStatusCallback); verify(mTestStatusCallback).notifyImsFeatureStatus(eq(ImsFeature.STATE_NOT_AVAILABLE)); } @Test @SmallTest public void testSetFeatureAndCheckCallback() throws Exception { mTestImsService.setImsFeatureStatusCallback(mTestStatusCallback); mTestImsService.testSetFeatureState(ImsFeature.STATE_READY); verify(mTestStatusCallback).notifyImsFeatureStatus(eq(ImsFeature.STATE_READY)); assertEquals(ImsFeature.STATE_READY, mTestImsService.getFeatureState()); } @Test @SmallTest public void testRegisterAndNotifyRemoveFeature() { mTestImsService.addFeatureRemovedListener(mTestRemovedCallback); mTestImsService.notifyFeatureRemoved(0); verify(mTestRemovedCallback).onFeatureRemoved(eq(0)); assertTrue(mTestImsService.featureRemovedCalled); } @Test @SmallTest public void testRegisterAndUnregisterNotify() { mTestImsService.addFeatureRemovedListener(mTestRemovedCallback); mTestImsService.removeFeatureRemovedListener(mTestRemovedCallback); mTestImsService.notifyFeatureRemoved(0); verify(mTestRemovedCallback, never()).onFeatureRemoved(eq(0)); assertTrue(mTestImsService.featureRemovedCalled); } }