Merge "Better handle MSIM DDS changed event" into oc-dev
Brad Ebinger [Thu, 4 May 2017 17:11:46 +0000 (17:11 +0000)]
src/java/com/android/internal/telephony/ims/ImsServiceController.java
src/java/com/android/internal/telephony/imsphone/ImsPhone.java
src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java

index 2df52c7..2362348 100644 (file)
@@ -184,7 +184,8 @@ public class ImsServiceController {
 
             @Override
             public void notifyImsFeatureStatus(int featureStatus) throws RemoteException {
-                Log.i(LOG_TAG, "notifyImsFeatureStatus");
+                Log.i(LOG_TAG, "notifyImsFeatureStatus: slot=" + mSlotId + ", feature="
+                        + mFeatureType + ", status=" + featureStatus);
                 sendImsFeatureStatusChanged(mSlotId, mFeatureType, featureStatus);
             }
         };
index 9ed3e89..0158dbb 100644 (file)
@@ -996,8 +996,8 @@ public class ImsPhone extends ImsPhoneBase {
         }
     }
 
-    /* package */
-    void sendErrorResponse(Message onComplete, Throwable e) {
+    @VisibleForTesting
+    public void sendErrorResponse(Message onComplete, Throwable e) {
         Rlog.d(LOG_TAG, "sendErrorResponse");
         if (onComplete != null) {
             AsyncResult.forMessage(onComplete, null, getCommandException(e));
index ebe88b0..3f171fa 100644 (file)
@@ -581,10 +581,25 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
      */
     private boolean mShouldUpdateImsConfigOnDisconnect = false;
 
-    private ImsServiceProxy.INotifyStatusChanged mNotifyFeatureRemovedCallback = () -> {
+    // Callback fires when ImsManager MMTel Feature changes state
+    private ImsServiceProxy.INotifyStatusChanged mNotifyStatusChangedCallback = () -> {
         try {
-            if (mImsManager.getImsServiceStatus() != ImsFeature.STATE_READY) {
-                retryGetImsService();
+            int status = mImsManager.getImsServiceStatus();
+            log("Status Changed: " + status);
+            switch(status) {
+                case ImsFeature.STATE_READY: {
+                    startListeningForCalls();
+                    break;
+                }
+                case ImsFeature.STATE_INITIALIZING:
+                    // fall through
+                case ImsFeature.STATE_NOT_AVAILABLE: {
+                    stopListeningForCalls();
+                    break;
+                }
+                default: {
+                    Log.w(LOG_TAG, "Unexpected State!");
+                }
             }
         } catch (ImsException e) {
             // Could not get the ImsService, retry!
@@ -592,6 +607,24 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
         }
     };
 
+    @VisibleForTesting
+    public interface IRetryTimeout {
+        int get();
+    }
+
+    /**
+     * Default implementation of interface that calculates the ImsService retry timeout.
+     * Override-able for testing.
+     */
+    @VisibleForTesting
+    public IRetryTimeout mRetryTimeout = () -> {
+        int timeout = (1 << mImsServiceRetryCount) * IMS_RETRY_STARTING_TIMEOUT_MS;
+        if (mImsServiceRetryCount <= CEILING_SERVICE_RETRY_COUNT) {
+            mImsServiceRetryCount++;
+        }
+        return timeout;
+    };
+
     //***** Events
 
 
@@ -627,13 +660,15 @@ 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);
-        }
+        // Adding to set, will be safe adding multiple times. If the ImsService is not active yet,
+        // this method will throw an ImsException.
+        mImsManager.addNotifyStatusChangedCallbackIfAvailable(mNotifyStatusChangedCallback);
+        // Wait for ImsService.STATE_READY to start listening for calls.
+        // Call the callback right away for compatibility with older devices that do not use states.
+        mNotifyStatusChangedCallback.notifyStatusChanged();
+    }
+
+    private void startListeningForCalls() throws ImsException {
         mImsServiceRetryCount = 0;
         mServiceId = mImsManager.open(ImsServiceClass.MMTEL,
                 createIncomingCallPendingIntent(),
@@ -648,9 +683,9 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
             mPhone.exitEmergencyCallbackMode();
         }
         int mPreferredTtyMode = Settings.Secure.getInt(
-            mPhone.getContext().getContentResolver(),
-            Settings.Secure.PREFERRED_TTY_MODE,
-            Phone.TTY_MODE_OFF);
+                mPhone.getContext().getContentResolver(),
+                Settings.Secure.PREFERRED_TTY_MODE,
+                Phone.TTY_MODE_OFF);
         mImsManager.setUiTTYMode(mPhone.getContext(), mPreferredTtyMode, null);
 
         ImsMultiEndpoint multiEndpoint = getMultiEndpointInterface();
@@ -660,6 +695,18 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
         }
     }
 
+    private void stopListeningForCalls() {
+        try {
+            // Only close on valid session.
+            if (mImsManager != null && mServiceId > 0) {
+                mImsManager.close(mServiceId);
+                mServiceId = -1;
+            }
+        } catch (ImsException e) {
+            // If the binder is unavailable, then the ImsService doesn't need to close.
+        }
+    }
+
     public void dispose() {
         if (DBG) log("dispose");
         mRingingCall.dispose();
@@ -968,6 +1015,7 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
             loge("dialInternal : " + e);
             conn.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
             sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
+            retryGetImsService();
         } catch (RemoteException e) {
         }
     }
@@ -1292,6 +1340,7 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
         } catch (ImsException e) {
             loge("setTTYMode : " + e);
             mPhone.sendErrorResponse(onComplete, e);
+            retryGetImsService();
         }
     }
 
@@ -1491,6 +1540,7 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
         } catch (ImsException e) {
             loge("sendUSSD : " + e);
             mPhone.sendErrorResponse(response, e);
+            retryGetImsService();
         }
     }
 
@@ -2835,16 +2885,16 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
     }
 
     private void retryGetImsService() {
+        // The binder connection is already up. Do not try to get it again.
+        if (mImsManager.isServiceAvailable()) {
+            return;
+        }
         //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++;
-        }
+        sendEmptyMessageDelayed(EVENT_GET_IMS_SERVICE, mRetryTimeout.get());
     }
 
     private void setVideoCallProvider(ImsPhoneConnection conn, ImsCall imsCall)
index c5a561b..b38659c 100644 (file)
@@ -80,6 +80,8 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 public abstract class TelephonyTest {
     protected static String TAG;
@@ -482,4 +484,29 @@ public abstract class TelephonyTest {
         doReturn(mPackageInfo).when(mPackageManager).getPackageInfoAsUser(
                 eq(TAG), anyInt(), anyInt());
     }
+
+
+    protected final void waitForHandlerAction(Handler h, long timeoutMillis) {
+        final CountDownLatch lock = new CountDownLatch(1);
+        h.post(lock::countDown);
+        while (lock.getCount() > 0) {
+            try {
+                lock.await(timeoutMillis, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                // do nothing
+            }
+        }
+    }
+
+    protected final void waitForHandlerActionDelayed(Handler h, long timeoutMillis, long delayMs) {
+        final CountDownLatch lock = new CountDownLatch(1);
+        h.postDelayed(lock::countDown, delayMs);
+        while (lock.getCount() > 0) {
+            try {
+                lock.await(timeoutMillis, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                // do nothing
+            }
+        }
+    }
 }
index 357d50e..923cd05 100644 (file)
 package com.android.internal.telephony.imsphone;
 
 import android.app.PendingIntent;
+import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Message;
 import android.support.test.filters.FlakyTest;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.ims.feature.ImsFeature;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.LargeTest;
 
 import com.android.ims.ImsCall;
 import com.android.ims.ImsCallProfile;
 import com.android.ims.ImsConfig;
 import com.android.ims.ImsConnectionStateListener;
+import com.android.ims.ImsException;
 import com.android.ims.ImsManager;
 import com.android.ims.ImsReasonInfo;
 import com.android.ims.ImsServiceClass;
 import com.android.ims.internal.ImsCallSession;
 import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyTest;
@@ -50,14 +55,20 @@ import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.isNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -73,6 +84,7 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest {
     private int mServiceId;
     @Mock
     private ImsCallSession mImsCallSession;
+    private Handler mCTHander;
 
     private class ImsCTHandlerThread extends HandlerThread {
 
@@ -87,6 +99,7 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest {
                     ImsReasonInfo.CODE_ANSWERED_ELSEWHERE);
             mCTUT.addReasonCodeRemapping(510, "Call answered elsewhere.",
                     ImsReasonInfo.CODE_ANSWERED_ELSEWHERE);
+            mCTHander = new Handler(mCTUT.getLooper());
             setReady(true);
         }
     }
@@ -191,8 +204,8 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest {
 
         waitUntilReady();
         logd("ImsPhoneCallTracker initiated");
-        /* Make sure getImsService is triggered on a separate thread */
-        waitForMs(100);
+        /* Make sure getImsService is triggered on handler */
+        waitForHandlerAction(mCTHander, 100);
     }
 
     @After
@@ -436,4 +449,49 @@ public class ImsPhoneCallTrackerTest extends TelephonyTest {
         assertEquals(90210, mCTUT.maybeRemapReasonCode(new ImsReasonInfo(90210, 1,
                 "Call answered elsewhere.")));
     }
+
+
+    @Test
+    @SmallTest
+    public void testDialImsServiceUnavailable() throws ImsException {
+        doThrow(new ImsException("Test Exception", ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN)).when(
+                mImsManager).createCallProfile(anyInt(), anyInt(), anyInt());
+        mCTUT.mRetryTimeout = () -> 0; //ms
+        assertEquals(Call.State.IDLE, mCTUT.mForegroundCall.getState());
+        assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
+
+        try {
+            mCTUT.dial("+17005554141", ImsCallProfile.CALL_TYPE_VOICE, null);
+        } catch (Exception e) {
+            Assert.fail();
+        }
+
+        // wait for handler to process ImsService connection retry
+        waitForHandlerAction(mCTHander, 1000); // 1 second timeout
+        verify(mImsManager, never()).makeCall(anyInt(), nullable(ImsCallProfile.class),
+                eq(new String[]{"+17005554141"}), nullable(ImsCall.Listener.class));
+        // Make sure that open is called in ImsPhoneCallTracker when it was first connected and
+        // again after retry.
+        verify(mImsManager, times(2)).open(anyInt(), nullable(PendingIntent.class),
+                nullable(ImsConnectionStateListener.class));
+    }
+
+    @Test
+    @SmallTest
+    public void testTTYImsServiceUnavailable() throws ImsException {
+        doThrow(new ImsException("Test Exception", ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN)).when(
+                mImsManager).setUiTTYMode(nullable(Context.class), anyInt(),
+                nullable(Message.class));
+        // Remove retry timeout delay
+        mCTUT.mRetryTimeout = () -> 0; //ms
+
+        mCTUT.setUiTTYMode(0, new Message());
+
+        // wait for handler to process ImsService connection retry
+        waitForHandlerAction(mCTHander, 100);
+        // Make sure that open is called in ImsPhoneCallTracker to re-establish connection to
+        // ImsService
+        verify(mImsManager, times(2)).open(anyInt(), nullable(PendingIntent.class),
+                nullable(ImsConnectionStateListener.class));
+    }
 }