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

Commit 16213446 authored by Amit Mahajan's avatar Amit Mahajan
Browse files

Timeout if carrier service does not respond.

Test: atest GsmSmsDispatcherTest
Bug: 178580734
Bug: 173632025
Change-Id: Ib051370096f238e7e1ad5725080c9a0fdce92d98
parent 0ceca794
Loading
Loading
Loading
Loading
+54 −3
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import android.os.AsyncResult;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.PersistableBundle;
import android.os.Process;
@@ -63,6 +64,8 @@ import android.text.Html;
import android.text.Spanned;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.IndentingPrintWriter;
import android.util.LocalLog;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -80,6 +83,8 @@ import com.android.internal.telephony.uicc.UiccCard;
import com.android.internal.telephony.uicc.UiccController;
import com.android.telephony.Rlog;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -149,6 +154,7 @@ public abstract class SMSDispatcher extends Handler {
    protected final CommandsInterface mCi;
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    protected final TelephonyManager mTelephonyManager;
    protected final LocalLog mLocalLog = new LocalLog(16);

    /** Maximum number of times to retry sending a failed SMS. */
    private static final int MAX_SEND_RETRIES = 3;
@@ -174,6 +180,9 @@ public abstract class SMSDispatcher extends Handler {
    protected boolean mSmsCapable = true;
    protected boolean mSmsSendDisabled;

    @VisibleForTesting
    public int mCarrierMessagingTimeout = 10 * 60 * 1000; //10 minutes

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    protected static int getNextConcatenatedRef() {
        sConcatenatedRef += 1;
@@ -344,19 +353,23 @@ public abstract class SMSDispatcher extends Handler {
    /**
     * Use the carrier messaging service to send a data or text SMS.
     */
    protected abstract class SmsSender {
    protected abstract class SmsSender extends Handler {
        private static final int EVENT_TIMEOUT = 1;
        protected final SmsTracker mTracker;
        // Initialized in sendSmsByCarrierApp
        protected volatile SmsSenderCallback mSenderCallback;
        protected final CarrierMessagingServiceWrapper mCarrierMessagingServiceWrapper =
                new CarrierMessagingServiceWrapper();
        private String mCarrierPackageName;

        protected SmsSender(SmsTracker tracker) {
            super(Looper.getMainLooper());
            mTracker = tracker;
        }

        public void sendSmsByCarrierApp(String carrierPackageName,
                                        SmsSenderCallback senderCallback) {
            mCarrierPackageName = carrierPackageName;
            mSenderCallback = senderCallback;
            if (!mCarrierMessagingServiceWrapper.bindToCarrierMessagingService(
                    mContext, carrierPackageName, runnable -> runnable.run(),
@@ -367,10 +380,33 @@ public abstract class SMSDispatcher extends Handler {
                        0 /* messageRef */);
            } else {
                Rlog.d(TAG, "bindService() for carrier messaging service succeeded");
                sendMessageDelayed(obtainMessage(EVENT_TIMEOUT), mCarrierMessagingTimeout);
            }
        }

        public abstract void onServiceReady();

        @Override
        public void handleMessage(Message msg) {
            if (msg.what == EVENT_TIMEOUT) {
                logWithLocalLog("handleMessage: did not receive response from "
                        + mCarrierPackageName + " for " + mCarrierMessagingTimeout + " ms");
                mSenderCallback.onSendSmsComplete(
                        CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK,
                        0 /* messageRef */);
            } else {
                logWithLocalLog("handleMessage: received unexpected message " + msg.what);
            }
        }

        public void removeTimeout() {
            removeMessages(EVENT_TIMEOUT);
        }
    }

    private void logWithLocalLog(String logStr) {
        mLocalLog.log(logStr);
        Rlog.d(TAG, logStr);
    }

    /**
@@ -398,7 +434,7 @@ public abstract class SMSDispatcher extends Handler {
                            runnable -> runnable.run(),
                            mSenderCallback);
                } catch (RuntimeException e) {
                    Rlog.e(TAG, "Exception sending the SMS: " + e);
                    Rlog.e(TAG, "Exception sending the SMS: " + e.getMessage());
                    mSenderCallback.onSendSmsComplete(
                            CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK,
                            0 /* messageRef */);
@@ -472,6 +508,7 @@ public abstract class SMSDispatcher extends Handler {
            try {
                mSmsSender.mCarrierMessagingServiceWrapper.disconnect();
                processSendSmsResponse(mSmsSender.mTracker, result, messageRef);
                mSmsSender.removeTimeout();
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
@@ -2476,4 +2513,18 @@ public abstract class SMSDispatcher extends Handler {
            Binder.restoreCallingIdentity(token);
        }
    }

    /**
     * Dump local logs
     */
    public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
        pw.println(TAG);
        pw.increaseIndent();
        pw.println("mLocalLog:");
        pw.increaseIndent();
        mLocalLog.dump(fd, pw, args);
        pw.decreaseIndent();
        pw.decreaseIndent();
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -1051,6 +1051,9 @@ public class SmsDispatchersController extends Handler {
    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        mGsmInboundSmsHandler.dump(fd, pw, args);
        mCdmaInboundSmsHandler.dump(fd, pw, args);
        mGsmDispatcher.dump(fd, pw, args);
        mCdmaDispatcher.dump(fd, pw, args);
        mImsSmsDispatcher.dump(fd, pw, args);
    }

    private void logd(String msg) {
+13 −3
Original line number Diff line number Diff line
@@ -101,6 +101,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

/**
 * Controls a test {@link Context} as would be provided by the Android framework to an
@@ -197,6 +198,9 @@ public class ContextFixture implements TestFixture<Context> {
                Intent serviceIntent,
                ServiceConnection connection,
                int flags) {
            if (mMockBindingFailureForPackage.contains(serviceIntent.getPackage())) {
                return false;
            }
            if (mServiceByServiceConnection.containsKey(connection)) {
                throw new RuntimeException("ServiceConnection already bound: " + connection);
            }
@@ -218,10 +222,11 @@ public class ContextFixture implements TestFixture<Context> {
        public void unbindService(
                ServiceConnection connection) {
            IInterface service = mServiceByServiceConnection.remove(connection);
            if (service == null) {
                throw new RuntimeException("ServiceConnection not found: " + connection);
            }
            if (service != null) {
                connection.onServiceDisconnected(mComponentNameByService.get(service));
            } else {
                logd("unbindService: ServiceConnection not found: " + connection);
            }
        }

        @Override
@@ -624,6 +629,7 @@ public class ContextFixture implements TestFixture<Context> {
    private final Map<ComponentName, IntentFilter> mIntentFilterByComponentName = new HashMap<>();
    private final Map<IInterface, ComponentName> mComponentNameByService =
            new HashMap<IInterface, ComponentName>();
    private final Set<String> mMockBindingFailureForPackage = new HashSet();
    private final Map<ServiceConnection, IInterface> mServiceByServiceConnection =
            new HashMap<ServiceConnection, IInterface>();
    private final Multimap<String, BroadcastReceiver> mBroadcastReceiversByAction =
@@ -791,6 +797,10 @@ public class ContextFixture implements TestFixture<Context> {
        mComponentNameByService.put(service, name);
    }

    public void mockBindingFailureForPackage(String packageName) {
        mMockBindingFailureForPackage.add(packageName);
    }

    private List<ResolveInfo> doQueryIntentServices(Intent intent, int flags) {
        List<ResolveInfo> result = new ArrayList<ResolveInfo>();
        for (ComponentName componentName : mComponentNamesByAction.get(intent.getAction())) {
+1 −0
Original line number Diff line number Diff line
@@ -589,6 +589,7 @@ public abstract class TelephonyTest {
                eq(UiccController.APP_FAM_3GPP2));
        doReturn(mUiccCardApplicationIms).when(mUiccController).getUiccCardApplication(anyInt(),
                eq(UiccController.APP_FAM_IMS));
        doReturn(mUiccCard).when(mUiccController).getUiccCard(anyInt());

        doAnswer(new Answer<IccRecords>() {
            public IccRecords answer(InvocationOnMock invocation) {
+139 −18
Original line number Diff line number Diff line
@@ -23,32 +23,44 @@ import static com.android.internal.telephony.SmsUsageMonitor.PREMIUM_SMS_PERMISS
import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.Activity;
import android.app.ActivityManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.location.Country;
import android.location.CountryDetector;
import android.os.HandlerThread;
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.provider.Settings;
import android.service.carrier.CarrierMessagingService;
import android.service.carrier.ICarrierMessagingCallback;
import android.service.carrier.ICarrierMessagingService;
import android.telephony.SmsManager;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.Singleton;

import androidx.test.filters.FlakyTest;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;

import com.android.internal.telephony.ContextFixture;
import com.android.internal.telephony.ISub;
@@ -62,15 +74,22 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class GsmSmsDispatcherTest extends TelephonyTest {

    private static final long TIMEOUT_MS = 500;
    private static final String CARRIER_APP_PACKAGE_NAME = "com.android.carrier";

    @Mock
    private android.telephony.SmsMessage mSmsMessage;
@@ -86,6 +105,9 @@ public class GsmSmsDispatcherTest extends TelephonyTest {
    private SMSDispatcher.SmsTracker mSmsTracker;
    @Mock
    private ISub.Stub mISubStub;
    @Mock
    private ICarrierMessagingService.Stub mICarrierAppMessagingService;

    private Object mLock = new Object();
    private boolean mReceivedTestIntent;
    private static final String TEST_INTENT = "com.android.internal.telephony.TEST_INTENT";
@@ -95,6 +117,7 @@ public class GsmSmsDispatcherTest extends TelephonyTest {
            logd("onReceive");
            synchronized (mLock) {
                mReceivedTestIntent = true;
                mLock.notifyAll();
            }
        }
    };
@@ -129,6 +152,9 @@ public class GsmSmsDispatcherTest extends TelephonyTest {
        mGsmSmsDispatcherTestHandler = new GsmSmsDispatcherTestHandler(getClass().getSimpleName());
        mGsmSmsDispatcherTestHandler.start();
        waitUntilReady();
        mGsmSmsDispatcher = new GsmSMSDispatcher(mPhone, mSmsDispatchersController,
                mGsmInboundSmsHandler);
        processAllMessages();
    }

    @After
@@ -142,7 +168,7 @@ public class GsmSmsDispatcherTest extends TelephonyTest {
    @Test @SmallTest
    public void testSmsStatus() {
        mSimulatedCommands.notifySmsStatus(new byte[]{(byte)0xFF, (byte)0xFF, (byte)0xFF});
        TelephonyTestUtils.waitForMs(50);
        processAllMessages();
        verify(mSimulatedCommandsVerifier).acknowledgeLastIncomingGsmSms(true, 0, null);
    }

@@ -202,18 +228,22 @@ public class GsmSmsDispatcherTest extends TelephonyTest {
        }
    }

    @Test
    @SmallTest
    @FlakyTest
    @Ignore
    public void testSendTextWithInvalidDestAddr() throws Exception {
    private void registerTestIntentReceiver() throws Exception {
        // unmock ActivityManager to be able to register receiver, create real PendingIntent and
        // receive TEST_INTENT
        restoreInstance(Singleton.class, "mInstance", mIActivityManagerSingleton);
        restoreInstance(ActivityManager.class, "IActivityManagerSingleton", null);
        Context realContext = TestApplication.getAppContext();
        realContext.registerReceiver(mTestReceiver, new IntentFilter(TEST_INTENT));
        PendingIntent pendingIntent = PendingIntent.getBroadcast(realContext, 0,
    }

    @Test
    @SmallTest
    @FlakyTest
    @Ignore
    public void testSendTextWithInvalidDestAddr() throws Exception {
        registerTestIntentReceiver();
        PendingIntent pendingIntent = PendingIntent.getBroadcast(TestApplication.getAppContext(), 0,
                new Intent(TEST_INTENT), 0);
        // send invalid dest address: +
        mReceivedTestIntent = false;
@@ -250,7 +280,8 @@ public class GsmSmsDispatcherTest extends TelephonyTest {
                Settings.Global.DEVICE_PROVISIONED, 1);

        mGsmSmsDispatcher.sendRawPdu(new SMSDispatcher.SmsTracker[] {mSmsTracker});
        waitForHandlerAction(mGsmSmsDispatcher, TIMEOUT_MS);
        //waitForHandlerAction(mGsmSmsDispatcher, TIMEOUT_MS);
        processAllMessages();

        verify(mSmsUsageMonitor, times(1)).checkDestination(any(), any());
        verify(mSmsUsageMonitor, times(1)).getPremiumSmsPermission(any());
@@ -264,13 +295,7 @@ public class GsmSmsDispatcherTest extends TelephonyTest {
    @FlakyTest
    @Ignore
    public void testSendMultipartTextWithInvalidText() throws Exception {
        // unmock ActivityManager to be able to register receiver, create real PendingIntent and
        // receive TEST_INTENT
        restoreInstance(Singleton.class, "mInstance", mIActivityManagerSingleton);
        restoreInstance(ActivityManager.class, "IActivityManagerSingleton", null);

        Context realContext = TestApplication.getAppContext();
        realContext.registerReceiver(mTestReceiver, new IntentFilter(TEST_INTENT));
        registerTestIntentReceiver();

        // initiate parameters for an invalid text MO SMS (the 2nd segmeant has 161 characters)
        ArrayList<String> parts = new ArrayList<>();
@@ -280,7 +305,7 @@ public class GsmSmsDispatcherTest extends TelephonyTest {
                + "8");

        ArrayList<PendingIntent> sentIntents = new ArrayList<>();
        PendingIntent sentIntent = PendingIntent.getBroadcast(realContext, 0,
        PendingIntent sentIntent = PendingIntent.getBroadcast(TestApplication.getAppContext(), 0,
                new Intent(TEST_INTENT), PendingIntent.FLAG_IMMUTABLE);
        sentIntents.add(sentIntent);
        sentIntents.add(sentIntent);
@@ -296,4 +321,100 @@ public class GsmSmsDispatcherTest extends TelephonyTest {
            assertEquals(SmsManager.RESULT_ERROR_GENERIC_FAILURE, mTestReceiver.getResultCode());
        }
    }

    private void mockCarrierApp()
            throws RemoteException {
        mContextFixture.addService(
                CarrierMessagingService.SERVICE_INTERFACE,
                new ComponentName(CARRIER_APP_PACKAGE_NAME, "CarrierAppFilterClass"),
                CARRIER_APP_PACKAGE_NAME,
                mICarrierAppMessagingService,
                new ServiceInfo());
        when(mICarrierAppMessagingService.asBinder()).thenReturn(mICarrierAppMessagingService);
        mockUiccWithCarrierApp();
    }

    private void mockUiccWithCarrierApp() {
        when(mUiccController.getUiccCard(mPhone.getPhoneId())).thenReturn(mUiccCard);
        List<String> carrierPackages = new ArrayList<>();
        carrierPackages.add(CARRIER_APP_PACKAGE_NAME);
        when(mUiccCard.getCarrierPackageNamesForIntent(
                any(PackageManager.class), any(Intent.class))).thenReturn(carrierPackages);
    }

    private void mockCarrierAppStubResults(final int result, ICarrierMessagingService.Stub stub,
            boolean callOnFilterComplete)
            throws RemoteException {
        when(stub.queryLocalInterface(anyString())).thenReturn(stub);
        when(stub.asBinder()).thenReturn(stub);
        doAnswer(new Answer<Void>() {
            @Override
            public Void answer(InvocationOnMock invocation) throws Throwable {
                Object[] args = invocation.getArguments();
                ICarrierMessagingCallback callback = (ICarrierMessagingCallback) args[4];
                if (callOnFilterComplete) {
                    callback.onSendSmsComplete(result, 0);
                }
                return null;
            }
        }).when(stub).sendTextSms(
                anyString(), anyInt(), anyString(), anyInt(),
                any(ICarrierMessagingCallback.class));
    }

    @Test
    @SmallTest
    public void testSendSmsByCarrierApp() throws Exception {
        mockCarrierApp();
        mockCarrierAppStubResults(CarrierMessagingService.SEND_STATUS_OK,
                mICarrierAppMessagingService, true);
        registerTestIntentReceiver();

        PendingIntent pendingIntent = PendingIntent.getBroadcast(TestApplication.getAppContext(), 0,
                new Intent(TEST_INTENT), PendingIntent.FLAG_MUTABLE);
        mReceivedTestIntent = false;

        mGsmSmsDispatcher.sendText("6501002000", "121" /*scAddr*/, "test sms",
                pendingIntent, null, null, null, false, -1, false, -1, false, 0L);
        processAllMessages();
        synchronized (mLock) {
            if (!mReceivedTestIntent) {
                // long wait since sometimes broadcasts can take a long time if the system is loaded
                mLock.wait(60000);
            }
            assertEquals(true, mReceivedTestIntent);
            int resultCode = mTestReceiver.getResultCode();
            assertTrue("Unexpected result code: " + resultCode,
                    resultCode == SmsManager.RESULT_ERROR_NONE || resultCode == Activity.RESULT_OK);
            verify(mSimulatedCommandsVerifier, times(0)).sendSMS(anyString(), anyString(),
                    any(Message.class));
        }
    }

    @Test
    @SmallTest
    public void testSendSmsByCarrierAppNoResponse() throws Exception {
        mockCarrierApp();
        // do not mock result, instead reduce the timeout for test
        mGsmSmsDispatcher.mCarrierMessagingTimeout = 100;

        mGsmSmsDispatcher.sendText("6501002000", "121" /*scAddr*/, "test sms",
                null, null, null, null, false, -1, false, -1, false, 0L);
        // wait for timeout
        waitForMs(150);
        verify(mSimulatedCommandsVerifier).sendSMS(anyString(), anyString(), any(Message.class));
    }

    @Test
    @SmallTest
    public void testSendSmsByCarrierAppBindingFailed() throws Exception {
        mContextFixture.mockBindingFailureForPackage(CARRIER_APP_PACKAGE_NAME);
        // mock presence of carrier app, but do not create a mock service to make binding fail
        mockUiccWithCarrierApp();

        mGsmSmsDispatcher.sendText("6501002000", "121" /*scAddr*/, "test sms",
                null, null, null, null, false, -1, false, -1, false, 0L);
        processAllMessages();
        verify(mSimulatedCommandsVerifier).sendSMS(anyString(), anyString(), any(Message.class));
    }
}