Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 80524b63 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Modifications to support ImsResolver and adds Tests"

parents 93c4fc94 4a424775
Loading
Loading
Loading
Loading
+13 −6
Original line number Diff line number Diff line
@@ -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;
@@ -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) {
@@ -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
+10 −7
Original line number Diff line number Diff line
@@ -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];
@@ -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();
                }
@@ -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();
            }
        }
    }
+58 −1
Original line number Diff line number Diff line
@@ -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;
@@ -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() {
@@ -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) {
@@ -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() {
+41 −18
Original line number Diff line number Diff line
@@ -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;
@@ -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

@@ -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


@@ -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);
@@ -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:
@@ -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 =
+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