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

Commit c9d93096 authored by Hwangoo Park's avatar Hwangoo Park
Browse files

Add domain selection connection for SMS

This is for a normal and an emergency SMS domain selection connection.
Each domain selection connection acts as a client to communicate with
the domain selection service.

Bug: 243377249
Test: atest SmsDomainSelectionConnectionTest
Test: atest EmergencySmsDomainSelectionConnectionTest
Test: manual (verify IMS/GSM/CDMA MO SMS for normal/emergency SMS when domain selection disabled)
Test: manual (verify retry SMS for normal/emergency SMS when domain selection disabled)
Test: manual (verify IMS/GSM/CDMA MO SMS for normal/emergency SMS when domain selection enabled)
Test: manual (verify retry SMS for normal/emergency SMS when domain selection enabled)
Change-Id: I75682ecadefbfd5b75a4658123ebb6eab93df3d3
parent 1c3a1f5d
Loading
Loading
Loading
Loading
+2 −3
Original line number Diff line number Diff line
@@ -150,12 +150,11 @@ public class DomainSelectionController {
                c = new NormalCallDomainSelectionConnection(phone, this);
            }
        } else if (selectorType == SELECTOR_TYPE_SMS) {
            // TODO(ag/20126511) uncomment when SmSDomainSelectionConnection is ready.
            /*if (isEmergency) {
            if (isEmergency) {
                c = new EmergencySmsDomainSelectionConnection(phone, this);
            } else {
                c = new SmsDomainSelectionConnection(phone, this);
            }*/
            }
        }

        addConnection(c);
+146 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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 com.android.internal.telephony.domainselection;

import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WLAN;
import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WWAN;

import android.annotation.NonNull;
import android.telephony.AccessNetworkConstants;
import android.telephony.AccessNetworkConstants.TransportType;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.data.ApnSetting;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.data.AccessNetworksManager;
import com.android.internal.telephony.emergency.EmergencyStateTracker;

/**
 * Manages the information of request and the callback binder for an emergency SMS.
 */
public class EmergencySmsDomainSelectionConnection extends SmsDomainSelectionConnection {
    private final Object mLock = new Object();
    private @NonNull EmergencyStateTracker mEmergencyStateTracker;
    private @TransportType int mPreferredTransportType =
            AccessNetworkConstants.TRANSPORT_TYPE_INVALID;

    public EmergencySmsDomainSelectionConnection(
            Phone phone, DomainSelectionController controller) {
        this(phone, controller, EmergencyStateTracker.getInstance());
    }

    @VisibleForTesting
    public EmergencySmsDomainSelectionConnection(Phone phone,
            DomainSelectionController controller, EmergencyStateTracker tracker) {
        super(phone, controller, true);
        mTag = "DomainSelectionConnection-EmergencySMS";
        mEmergencyStateTracker = tracker;
    }

    @Override
    public void onWlanSelected() {
        synchronized (mLock) {
            if (mPreferredTransportType != AccessNetworkConstants.TRANSPORT_TYPE_INVALID) {
                logi("Domain selection completion is in progress");
                return;
            }

            mEmergencyStateTracker.onEmergencyTransportChanged(MODE_EMERGENCY_WLAN);

            // Change the transport type if the current preferred transport type for an emergency
            // is not {@link AccessNetworkConstants#TRANSPORT_TYPE_WLAN}.
            AccessNetworksManager anm = mPhone.getAccessNetworksManager();
            if (anm.getPreferredTransport(ApnSetting.TYPE_EMERGENCY)
                    != AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
                changePreferredTransport(AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
                // The {@link #onDomainSlected()} will be called after the preferred transport
                // is successfully changed and notified from the {@link AccessNetworksManager}.
                return;
            }

            super.onWlanSelected();
        }
    }

    @Override
    public void onWwanSelected() {
        mEmergencyStateTracker.onEmergencyTransportChanged(MODE_EMERGENCY_WWAN);
    }

    @Override
    public void onDomainSelected(@NetworkRegistrationInfo.Domain int domain) {
        synchronized (mLock) {
            if (mPreferredTransportType != AccessNetworkConstants.TRANSPORT_TYPE_INVALID) {
                logi("Domain selection completion is in progress");
                return;
            }

            if (domain == NetworkRegistrationInfo.DOMAIN_PS) {
                // Change the transport type if the current preferred transport type for
                // an emergency is not {@link AccessNetworkConstants#TRANSPORT_TYPE_WWAN}.
                AccessNetworksManager anm = mPhone.getAccessNetworksManager();
                if (anm.getPreferredTransport(ApnSetting.TYPE_EMERGENCY)
                        != AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
                    changePreferredTransport(AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
                    // The {@link #onDomainSlected()} will be called after the preferred transport
                    // is successfully changed and notified from the {@link AccessNetworksManager}.
                    return;
                }
            }

            super.onDomainSelected(domain);
        }
    }

    @Override
    public void finishSelection() {
        AccessNetworksManager anm = mPhone.getAccessNetworksManager();

        synchronized (mLock) {
            if (mPreferredTransportType != AccessNetworkConstants.TRANSPORT_TYPE_INVALID) {
                mPreferredTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
                anm.unregisterForQualifiedNetworksChanged(mHandler);
            }
        }

        super.finishSelection();
    }

    @Override
    protected void onQualifiedNetworksChanged() {
        AccessNetworksManager anm = mPhone.getAccessNetworksManager();
        int preferredTransportType = anm.getPreferredTransport(ApnSetting.TYPE_EMERGENCY);

        synchronized (mLock) {
            if (preferredTransportType == mPreferredTransportType) {
                mPreferredTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
                super.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS);
                anm.unregisterForQualifiedNetworksChanged(mHandler);
            }
        }
    }

    private void changePreferredTransport(@TransportType int transportType) {
        logi("Change preferred transport: " + transportType);
        initHandler();
        mPreferredTransportType = transportType;
        AccessNetworksManager anm = mPhone.getAccessNetworksManager();
        anm.registerForQualifiedNetworksChanged(mHandler, EVENT_QUALIFIED_NETWORKS_CHANGED);
        mPhone.notifyEmergencyDomainSelected(transportType);
    }
}
+83 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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 com.android.internal.telephony.domainselection;

import static android.telephony.DomainSelectionService.SELECTOR_TYPE_SMS;

import android.annotation.NonNull;
import android.telephony.Annotation.DisconnectCauses;
import android.telephony.DomainSelectionService;
import android.telephony.NetworkRegistrationInfo;

import com.android.internal.telephony.Phone;

import java.util.concurrent.CompletableFuture;

/**
 * Manages the information of request and the callback binder for SMS.
 */
public class SmsDomainSelectionConnection extends DomainSelectionConnection {
    private DomainSelectionConnectionCallback mCallback;

    public SmsDomainSelectionConnection(Phone phone, DomainSelectionController controller) {
        this(phone, controller, false);
        mTag = "DomainSelectionConnection-SMS";
    }

    protected SmsDomainSelectionConnection(Phone phone, DomainSelectionController controller,
            boolean isEmergency) {
        super(phone, SELECTOR_TYPE_SMS, isEmergency, controller);
    }

    @Override
    public void onWlanSelected() {
        super.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS);
    }

    @Override
    public void onSelectionTerminated(@DisconnectCauses int cause) {
        if (mCallback != null) mCallback.onSelectionTerminated(cause);
    }

    @Override
    public void finishSelection() {
        CompletableFuture<Integer> future = getCompletableFuture();

        if (future != null && !future.isDone()) {
            cancelSelection();
        } else {
            super.finishSelection();
        }
    }

    /**
     * Requests a domain selection for SMS.
     *
     * @param attr The attributes required to determine the domain.
     * @param callback A callback to notify an error of the domain selection.
     * @return A {@link CompletableFuture} to get the selected domain
     *         {@link NetworkRegistrationInfo#DOMAIN_PS} or
     *         {@link NetworkRegistrationInfo#DOMAIN_CS}.
     */
    public @NonNull CompletableFuture<Integer> requestDomainSelection(
            @NonNull DomainSelectionService.SelectionAttributes attr,
            @NonNull DomainSelectionConnectionCallback callback) {
        mCallback = callback;
        selectDomain(attr);
        return getCompletableFuture();
    }
}
+399 −0

File added.

Preview size limit exceeded, changes collapsed.

+212 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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 com.android.internal.telephony.domainselection;

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.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;

import android.os.Handler;
import android.os.HandlerThread;
import android.telephony.DisconnectCause;
import android.telephony.DomainSelectionService;
import android.telephony.DomainSelector;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.TransportSelectorCallback;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.TestableLooper;

import androidx.test.runner.AndroidJUnit4;

import com.android.internal.telephony.Phone;

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 java.util.concurrent.CompletableFuture;

/**
 * Unit tests for SmsDomainSelectionConnection.
 */
@RunWith(AndroidJUnit4.class)
public class SmsDomainSelectionConnectionTest {
    private static final int SLOT_ID = 0;
    private static final int SUB_ID = 1;

    @Mock private Phone mPhone;
    @Mock private DomainSelectionController mDsController;
    @Mock private DomainSelectionConnection.DomainSelectionConnectionCallback mDscCallback;
    @Mock private DomainSelector mDomainSelector;

    private Handler mHandler;
    private TestableLooper mTestableLooper;
    private DomainSelectionService.SelectionAttributes mDsAttr;
    private SmsDomainSelectionConnection mDsConnection;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);

        HandlerThread handlerThread = new HandlerThread(
                SmsDomainSelectionConnectionTest.class.getSimpleName());
        handlerThread.start();

        mHandler = new Handler(handlerThread.getLooper());
        mDsConnection = new SmsDomainSelectionConnection(mPhone, mDsController);
        mDsConnection.getTransportSelectorCallback().onCreated(mDomainSelector);
        mDsAttr = new DomainSelectionService.SelectionAttributes.Builder(
                SLOT_ID, SUB_ID, DomainSelectionService.SELECTOR_TYPE_SMS).build();
    }

    @After
    public void tearDown() throws Exception {
        if (mTestableLooper != null) {
            mTestableLooper.destroy();
            mTestableLooper = null;
        }

        if (mHandler != null) {
            mHandler.getLooper().quit();
            mHandler = null;
        }

        mDomainSelector = null;
        mDsAttr = null;
        mDsConnection = null;
        mDscCallback = null;
        mDsController = null;
        mPhone = null;
    }

    @Test
    @SmallTest
    public void testRequestDomainSelection() {
        CompletableFuture<Integer> future =
                mDsConnection.requestDomainSelection(mDsAttr, mDscCallback);

        assertNotNull(future);
        verify(mDsController).selectDomain(eq(mDsAttr), any(TransportSelectorCallback.class));
    }

    @Test
    @SmallTest
    public void testOnWlanSelected() throws Exception {
        setUpTestableLooper();
        CompletableFuture<Integer> future =
                mDsConnection.requestDomainSelection(mDsAttr, mDscCallback);
        future.thenAcceptAsync((domain) -> {
            assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain);
        }, mHandler::post);

        mDsConnection.onWlanSelected();
        processAllMessages();

        assertTrue(future.isDone());
    }

    @Test
    @SmallTest
    public void testOnSelectionTerminated() {
        CompletableFuture<Integer> future =
                mDsConnection.requestDomainSelection(mDsAttr, mDscCallback);
        mDsConnection.onSelectionTerminated(DisconnectCause.LOCAL);

        assertFalse(future.isDone());
        verify(mDscCallback).onSelectionTerminated(eq(DisconnectCause.LOCAL));
    }

    @Test
    @SmallTest
    public void testOnDomainSelectedPs() throws Exception {
        setUpTestableLooper();
        CompletableFuture<Integer> future =
                mDsConnection.requestDomainSelection(mDsAttr, mDscCallback);
        future.thenAcceptAsync((domain) -> {
            assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain);
        }, mHandler::post);

        mDsConnection.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS);
        processAllMessages();

        assertTrue(future.isDone());
    }

    @Test
    @SmallTest
    public void testOnDomainSelectedCs() throws Exception {
        setUpTestableLooper();
        CompletableFuture<Integer> future =
                mDsConnection.requestDomainSelection(mDsAttr, mDscCallback);
        future.thenAcceptAsync((domain) -> {
            assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_CS), domain);
        }, mHandler::post);

        mDsConnection.onDomainSelected(NetworkRegistrationInfo.DOMAIN_CS);
        processAllMessages();

        assertTrue(future.isDone());
    }

    @Test
    @SmallTest
    public void testFinishSelection() throws Exception {
        setUpTestableLooper();
        CompletableFuture<Integer> future =
                mDsConnection.requestDomainSelection(mDsAttr, mDscCallback);
        future.thenAcceptAsync((domain) -> {
            assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain);
        }, mHandler::post);

        mDsConnection.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS);
        processAllMessages();
        mDsConnection.finishSelection();

        verify(mDomainSelector).finishSelection();
    }

    @Test
    @SmallTest
    public void testCancelSelection() throws Exception {
        CompletableFuture<Integer> future =
                mDsConnection.requestDomainSelection(mDsAttr, mDscCallback);
        future.thenAcceptAsync((domain) -> {
            assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain);
        }, mHandler::post);

        mDsConnection.finishSelection();

        verify(mDomainSelector).cancelSelection();
    }

    private void setUpTestableLooper() throws Exception {
        mTestableLooper = new TestableLooper(mHandler.getLooper());
    }

    private void processAllMessages() {
        while (!mTestableLooper.getLooper().getQueue().isIdle()) {
            mTestableLooper.processAllMessages();
        }
    }
}