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

Commit 7ce36f41 authored by Hunsuk Choi's avatar Hunsuk Choi
Browse files

Support domain selection service plug-in architecture

DomainSelectionService can be served in a separate process as a plug-in.
DomainSelectionController shall bind a remote service.

Bug: 258112541
Test: atest DomainSelectionControllerTest
Change-Id: I565c8587288219f1418d188fdfe2a9a79b7f8fdf
parent a92dca74
Loading
Loading
Loading
Loading
+101 −57
Original line number Diff line number Diff line
@@ -19,10 +19,10 @@ package com.android.internal.telephony.domainselection;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.AsyncResult;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.telephony.AccessNetworkConstants;
import android.telephony.AccessNetworkConstants.AccessNetworkType;
import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
@@ -34,14 +34,17 @@ import android.telephony.DomainSelectionService.EmergencyScanType;
import android.telephony.DomainSelector;
import android.telephony.EmergencyRegResult;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.TransportSelectorCallback;
import android.telephony.WwanSelectorCallback;
import android.telephony.data.ApnSetting;
import android.util.LocalLog;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.telephony.IDomainSelector;
import com.android.internal.telephony.ITransportSelectorCallback;
import com.android.internal.telephony.ITransportSelectorResultCallback;
import com.android.internal.telephony.IWwanSelectorCallback;
import com.android.internal.telephony.IWwanSelectorResultCallback;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.data.AccessNetworksManager.QualifiedNetworks;
import com.android.internal.telephony.util.TelephonyUtils;
@@ -49,7 +52,6 @@ import com.android.internal.telephony.util.TelephonyUtils;
import java.io.PrintWriter;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;


/**
@@ -73,14 +75,20 @@ public class DomainSelectionConnection {
        void onSelectionTerminated(@DisconnectCauses int cause);
    }

    /** An internal class implementing {@link TransportSelectorCallback} interface. */
    private final class TransportSelectorCallbackWrapper implements TransportSelectorCallback {
    /**
     * A wrapper class for {@link ITransportSelectorCallback} interface.
     */
    private final class TransportSelectorCallbackAdaptor extends ITransportSelectorCallback.Stub {
        @Override
        public void onCreated(@NonNull DomainSelector selector) {
        public void onCreated(@NonNull IDomainSelector selector) {
            synchronized (mLock) {
                mDomainSelector = selector;
                if (mDisposed) {
                    mDomainSelector.cancelSelection();
                    try {
                        selector.cancelSelection();
                    } catch (RemoteException e) {
                        // ignore exception
                    }
                    return;
                }
                DomainSelectionConnection.this.onCreated();
@@ -96,10 +104,10 @@ public class DomainSelectionConnection {
        }

        @Override
        public @NonNull WwanSelectorCallback onWwanSelected() {
        public @NonNull IWwanSelectorCallback onWwanSelected() {
            synchronized (mLock) {
                if (mWwanSelectorCallback == null) {
                    mWwanSelectorCallback = new WwanSelectorCallbackWrapper();
                    mWwanSelectorCallback = new WwanSelectorCallbackAdaptor();
                }
                if (mDisposed) {
                    return mWwanSelectorCallback;
@@ -110,18 +118,22 @@ public class DomainSelectionConnection {
        }

        @Override
        public void onWwanSelected(final Consumer<WwanSelectorCallback> consumer) {
        public void onWwanSelectedAsync(@NonNull final ITransportSelectorResultCallback cb) {
            synchronized (mLock) {
                if (mDisposed) return;
                if (mWwanSelectorCallback == null) {
                    mWwanSelectorCallback = new WwanSelectorCallbackWrapper();
                    mWwanSelectorCallback = new WwanSelectorCallbackAdaptor();
                }
                initHandler();
                mHandler.post(() -> {
                    synchronized (mLock) {
                        if (mDisposed) return;
                        DomainSelectionConnection.this.onWwanSelected();
                        consumer.accept(mWwanSelectorCallback);
                    }
                    try {
                        cb.onCompleted(mWwanSelectorCallback);
                    } catch (RemoteException e) {
                        loge("onWwanSelectedAsync executor exception=" + e);
                    }
                });
            }
@@ -137,23 +149,22 @@ public class DomainSelectionConnection {
        }
    }

    /** An internal class implementing {@link WwanSelectorCallback} interface. */
    private final class WwanSelectorCallbackWrapper
            implements WwanSelectorCallback, CancellationSignal.OnCancelListener {
    /**
     * A wrapper class for {@link IWwanSelectorCallback} interface.
     */
    private final class WwanSelectorCallbackAdaptor extends IWwanSelectorCallback.Stub {
        @Override
        public void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks,
                @EmergencyScanType int scanType, @NonNull CancellationSignal signal,
                @NonNull Consumer<EmergencyRegResult> consumer) {
        public void onRequestEmergencyNetworkScan(
                @NonNull @RadioAccessNetworkType int[] preferredNetworks,
                @EmergencyScanType int scanType, @NonNull IWwanSelectorResultCallback cb) {
            synchronized (mLock) {
                if (mDisposed) return;
                if (signal != null) signal.setOnCancelListener(this);
                mResultCallback = consumer;
                mResultCallback = cb;
                initHandler();
                mHandler.post(() -> {
                    synchronized (mLock) {
                        DomainSelectionConnection.this.onRequestEmergencyNetworkScan(
                                preferredNetworks.stream().mapToInt(Integer::intValue).toArray(),
                                scanType);
                                preferredNetworks, scanType);
                    }
                });
            }
@@ -189,14 +200,19 @@ public class DomainSelectionConnection {
            AsyncResult ar;
            switch (msg.what) {
                case EVENT_EMERGENCY_NETWORK_SCAN_RESULT:
                    mIsWaitingForScanResult = false;
                    if (mResultCallback == null) break;
                    ar = (AsyncResult) msg.obj;
                    EmergencyRegResult regResult = (EmergencyRegResult) ar.result;
                    if (DBG) logd("EVENT_EMERGENCY_NETWORK_SCAN_RESULT result=" + regResult);
                    CompletableFuture.runAsync(
                            () -> mResultCallback.accept(regResult),
                            mController.getDomainSelectionServiceExecutor());
                    synchronized (mLock) {
                        mIsWaitingForScanResult = false;
                        if (mResultCallback != null) {
                            try {
                                mResultCallback.onComplete(regResult);
                            } catch (RemoteException e) {
                                loge("EVENT_EMERGENCY_NETWORK_SCAN_RESULT exception=" + e);
                            }
                        }
                    }
                    break;
                case EVENT_QUALIFIED_NETWORKS_CHANGED:
                    ar = (AsyncResult) msg.obj;
@@ -218,7 +234,7 @@ public class DomainSelectionConnection {
    private boolean mDisposed = false;
    private final Object mLock = new Object();
    private final LocalLog mLocalLog = new LocalLog(30);
    private final @NonNull TransportSelectorCallback mTransportSelectorCallback;
    private final @NonNull ITransportSelectorCallback mTransportSelectorCallback;

    /**
     * Controls the communication between {@link DomainSelectionConnection} and
@@ -229,11 +245,11 @@ public class DomainSelectionConnection {
    private final boolean mIsEmergency;

    /** Interface to receive the request to trigger emergency network scan and selected domain. */
    private @Nullable WwanSelectorCallback mWwanSelectorCallback;
    private @Nullable IWwanSelectorCallback mWwanSelectorCallback;
    /** Interface to return the result of emergency network scan. */
    private @Nullable Consumer<EmergencyRegResult> mResultCallback;
    private @Nullable IWwanSelectorResultCallback mResultCallback;
    /** Interface to the {@link DomainSelector} created for this service. */
    private @Nullable DomainSelector mDomainSelector;
    private @Nullable IDomainSelector mDomainSelector;

    /** The slot requested this connection. */
    protected @NonNull Phone mPhone;
@@ -267,7 +283,7 @@ public class DomainSelectionConnection {
        mIsEmergency = isEmergency;
        mLooper = Looper.getMainLooper();

        mTransportSelectorCallback = new TransportSelectorCallbackWrapper();
        mTransportSelectorCallback = new TransportSelectorCallbackAdaptor();
        mOnComplete = new AndroidFuture<>();
    }

@@ -281,15 +297,23 @@ public class DomainSelectionConnection {
    }

    /**
     * Returns the interface for the callbacks.
     * Returns the callback binder interface.
     *
     * @return The {@link TransportSelectorCallback} interface.
     * @return The {@link ITransportSelectorCallback} interface.
     */
    @VisibleForTesting
    public @NonNull TransportSelectorCallback getTransportSelectorCallback() {
    public @Nullable ITransportSelectorCallback getTransportSelectorCallback() {
        return mTransportSelectorCallback;
    }

    /**
     * Returns the callback binder interface to handle the emergency scan result.
     *
     * @return The {@link IWwanSelectorResultCallback} interface.
     */
    public @Nullable IWwanSelectorResultCallback getEmergencyRegResultCallback() {
        return mResultCallback;
    }

    /**
     * Returns the {@link CompletableFuture} to receive the selected domain.
     *
@@ -316,7 +340,7 @@ public class DomainSelectionConnection {
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
    public void selectDomain(@NonNull DomainSelectionService.SelectionAttributes attr) {
        mSelectionAttributes = attr;
        mController.selectDomain(attr, getTransportSelectorCallback());
        mController.selectDomain(attr, mTransportSelectorCallback);
    }

    /**
@@ -369,9 +393,10 @@ public class DomainSelectionConnection {
    public void onRequestEmergencyNetworkScan(
            @NonNull @RadioAccessNetworkType int[] preferredNetworks,
            @EmergencyScanType int scanType) {
        if (mHandler == null) return;

        // Can be overridden if required
        synchronized (mLock) {
            if (mDisposed || mHandler == null) return;

            if (!mRegisteredRegistrant) {
                mPhone.registerForEmergencyNetworkScan(mHandler,
                        EVENT_EMERGENCY_NETWORK_SCAN_RESULT, null);
@@ -380,6 +405,7 @@ public class DomainSelectionConnection {
            mIsWaitingForScanResult = true;
            mPhone.triggerEmergencyNetworkScan(preferredNetworks, scanType, null);
        }
    }

    /**
     * Notifies the domain selected.
@@ -425,12 +451,17 @@ public class DomainSelectionConnection {
     */
    public void cancelSelection() {
        synchronized (mLock) {
            try {
                if (mDomainSelector != null) {
                    mDomainSelector.cancelSelection();
                }
            } catch (RemoteException e) {
                loge("cancelSelection exception=" + e);
            } finally {
                dispose();
            }
        }
    }

    /**
     * Requests the domain selection service to reselect a domain.
@@ -440,24 +471,37 @@ public class DomainSelectionConnection {
     */
    public @NonNull CompletableFuture<Integer> reselectDomain(
            @NonNull DomainSelectionService.SelectionAttributes attr) {
        synchronized (mLock) {
            mSelectionAttributes = attr;
        if (mDomainSelector == null) return null;
            mOnComplete = new AndroidFuture<>();
            try {
                if (mDomainSelector != null) {
                    mDomainSelector.reselectDomain(attr);
                }
            } catch (RemoteException e) {
                loge("reselectDomain exception=" + e);
            } finally {
                return mOnComplete;
            }
        }
    }

    /**
     * Finishes the selection procedure and cleans everything up.
     */
    public void finishSelection() {
        synchronized (mLock) {
            try {
                if (mDomainSelector != null) {
                    mDomainSelector.finishSelection();
                }
            } catch (RemoteException e) {
                loge("finishSelection exception=" + e);
            } finally {
                dispose();
            }
        }
    }

    /** Indicates that the service connection has been removed. */
    public void onServiceDisconnected() {
+201 −25
Original line number Diff line number Diff line
@@ -21,27 +21,32 @@ import static android.telephony.DomainSelectionService.SELECTOR_TYPE_SMS;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.telephony.BarringInfo;
import android.telephony.DomainSelectionService;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.telephony.TransportSelectorCallback;
import android.util.LocalLog;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IDomainSelectionServiceController;
import com.android.internal.telephony.ITransportSelectorCallback;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.util.TelephonyUtils;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.concurrent.Executor;

/**
 * Manages the connection to {@link DomainSelectionService}.
@@ -56,7 +61,6 @@ public class DomainSelectionController {
    private final HandlerThread mHandlerThread =
            new HandlerThread("DomainSelectionControllerHandler");

    private final DomainSelectionService mDomainSelectionService;
    private final Handler mHandler;
    // Only added or removed, never accessed on purpose.
    private final LocalLog mLocalLog = new LocalLog(30);
@@ -67,6 +71,92 @@ public class DomainSelectionController {
    protected final int[] mConnectionCounts;
    private final ArrayList<DomainSelectionConnection> mConnections = new ArrayList<>();

    private ComponentName mComponentName;
    private DomainSelectionServiceConnection mServiceConnection;
    private IDomainSelectionServiceController mIServiceController;
    // Binding the service is in progress or the service is bound already.
    private boolean mIsBound = false;

    private class DomainSelectionServiceConnection implements ServiceConnection {
        // Track the status of whether or not the Service has died in case we need to permanently
        // unbind (see onNullBinding below).
        private boolean mIsServiceConnectionDead = false;

        /** {@inheritDoc} */
        @Override
        public void onServiceConnected(ComponentName unusedName, IBinder service) {
            if (mHandler.getLooper().isCurrentThread()) {
                onServiceConnectedInternal(service);
            } else {
                mHandler.post(() -> onServiceConnectedInternal(service));
            }
        }

        /** {@inheritDoc} */
        @Override
        public void onServiceDisconnected(ComponentName unusedName) {
            if (mHandler.getLooper().isCurrentThread()) {
                onServiceDisconnectedInternal();
            } else {
                mHandler.post(() -> onServiceDisconnectedInternal());
            }
        }

        @Override
        public void onBindingDied(ComponentName unusedName) {
            if (mHandler.getLooper().isCurrentThread()) {
                onBindingDiedInternal();
            } else {
                mHandler.post(() -> onBindingDiedInternal());
            }
        }

        @Override
        public void onNullBinding(ComponentName unusedName) {
            if (mHandler.getLooper().isCurrentThread()) {
                onNullBindingInternal();
            } else {
                mHandler.post(() -> onNullBindingInternal());
            }
        }

        private void onServiceConnectedInternal(IBinder service) {
            synchronized (mLock) {
                logi("onServiceConnected with binder: " + service);
                setServiceController(service);
            }
        }

        private void onServiceDisconnectedInternal() {
            synchronized (mLock) {
                setServiceController(null);
            }
            logi("onServiceDisconnected");
        }

        private void onBindingDiedInternal() {
            mIsServiceConnectionDead = true;
            synchronized (mLock) {
                mIsBound = false;
                setServiceController(null);
                unbindService();
            }
            loge("onBindingDied");
        }

        private void onNullBindingInternal() {
            loge("onNullBinding serviceDead=" + mIsServiceConnectionDead);
            // onNullBinding will happen after onBindingDied. In this case, we should not
            // permanently unbind and instead let the automatic rebind occur.
            if (mIsServiceConnectionDead) return;
            synchronized (mLock) {
                mIsBound = false;
                setServiceController(null);
                unbindService();
            }
        }
    }

    private final class DomainSelectionControllerHandler extends Handler {
        DomainSelectionControllerHandler(Looper looper) {
            super(looper);
@@ -95,25 +185,20 @@ public class DomainSelectionController {
     * Creates an instance.
     *
     * @param context Context object from hosting application.
     * @param service The {@link DomainSelectionService} instance.
     */
    public DomainSelectionController(@NonNull Context context,
            @NonNull DomainSelectionService service) {
        this(context, service, null);
    public DomainSelectionController(@NonNull Context context) {
        this(context, null);
    }

    /**
     * Creates an instance.
     *
     * @param context Context object from hosting application.
     * @param service The {@link DomainSelectionService} instance.
     * @param looper Handles event messages.
     */
    @VisibleForTesting
    public DomainSelectionController(@NonNull Context context,
            @NonNull DomainSelectionService service, @Nullable Looper looper) {
    public DomainSelectionController(@NonNull Context context, @Nullable Looper looper) {
        mContext = context;
        mDomainSelectionService = service;

        if (looper == null) {
            mHandlerThread.start();
@@ -183,12 +268,19 @@ public class DomainSelectionController {
     * @param callback A callback to receive the response.
     */
    public void selectDomain(@NonNull DomainSelectionService.SelectionAttributes attr,
            @NonNull TransportSelectorCallback callback) {
        if (attr == null || callback == null) return;
            @NonNull ITransportSelectorCallback callback) {
        if (attr == null) return;
        if (DBG) logd("selectDomain");

        Executor e = mDomainSelectionService.getCachedExecutor();
        e.execute(() -> mDomainSelectionService.onDomainSelection(attr, callback));
        synchronized (mLock) {
            try  {
                if (mIServiceController != null) {
                    mIServiceController.selectDomain(attr, callback);
                }
            } catch (RemoteException e) {
                loge("selectDomain e=" + e);
            }
        }
    }

    /**
@@ -201,9 +293,16 @@ public class DomainSelectionController {
        if (phone == null || serviceState == null) return;
        if (DBG) logd("updateServiceState phoneId=" + phone.getPhoneId());

        Executor e = mDomainSelectionService.getCachedExecutor();
        e.execute(() -> mDomainSelectionService.onServiceStateUpdated(
                phone.getPhoneId(), phone.getSubId(), serviceState));
        synchronized (mLock) {
            try  {
                if (mIServiceController != null) {
                    mIServiceController.updateServiceState(
                            phone.getPhoneId(), phone.getSubId(), serviceState);
                }
            } catch (RemoteException e) {
                loge("updateServiceState e=" + e);
            }
        }
    }

    /**
@@ -216,9 +315,16 @@ public class DomainSelectionController {
        if (phone == null || info == null) return;
        if (DBG) logd("updateBarringInfo phoneId=" + phone.getPhoneId());

        Executor e = mDomainSelectionService.getCachedExecutor();
        e.execute(() -> mDomainSelectionService.onBarringInfoUpdated(
                phone.getPhoneId(), phone.getSubId(), info));
        synchronized (mLock) {
            try  {
                if (mIServiceController != null) {
                    mIServiceController.updateBarringInfo(
                            phone.getPhoneId(), phone.getSubId(), info);
                }
            } catch (RemoteException e) {
                loge("updateBarringInfo e=" + e);
            }
        }
    }

    /**
@@ -269,11 +375,81 @@ public class DomainSelectionController {
    }

    /**
     * Gets the {@link Executor} which executes methods of {@link DomainSelectionService.}
     * @return {@link Executor} instance.
     * Sets the binder interface to communicate with {@link domainSelectionService}.
     */
    public @NonNull Executor getDomainSelectionServiceExecutor() {
        return mDomainSelectionService.getCachedExecutor();
    protected void setServiceController(@NonNull IBinder serviceController) {
        mIServiceController = IDomainSelectionServiceController.Stub.asInterface(serviceController);
    }

    /**
     * Sends request to bind to {@link DomainSelectionService}.
     *
     * @param componentName The {@link ComponentName} instance.
     * @return {@code true} if the service is in the process of being bound, {@code false} if it
     *         has failed.
     */
    public boolean bind(@NonNull ComponentName componentName) {
        mComponentName = componentName;
        return bind();
    }

    private boolean bind() {
        logd("bind isBindingOrBound=" + mIsBound);
        synchronized (mLock) {
            if (!mIsBound) {
                mIsBound = true;
                Intent serviceIntent = new Intent(DomainSelectionService.SERVICE_INTERFACE)
                        .setComponent(mComponentName);
                mServiceConnection = new DomainSelectionServiceConnection();
                int serviceFlags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
                        | Context.BIND_IMPORTANT;
                logi("binding DomainSelectionService");
                try {
                    boolean bindSucceeded = mContext.bindService(serviceIntent,
                            mServiceConnection, serviceFlags);
                    if (!bindSucceeded) {
                        loge("binding failed");
                        mIsBound = false;
                    }
                    return bindSucceeded;
                } catch (Exception e) {
                    mIsBound = false;
                    loge("binding e=" + e.getMessage());
                    return false;
                }
            } else {
                return false;
            }
        }
    }

    /**
     * Unbinds the service.
     */
    public void unbind() {
        synchronized (mLock) {
            mIsBound = false;
            setServiceController(null);
            unbindService();
        }
    }

    private void unbindService() {
        synchronized (mLock) {
            if (mServiceConnection != null) {
                logi("unbinding Service");
                mContext.unbindService(mServiceConnection);
                mServiceConnection = null;
            }
        }
    }

    /**
     * Returns the Handler instance.
     */
    @VisibleForTesting
    public Handler getHandlerForTest() {
        return mHandler;
    }

    /**
+98 −74

File changed.

Preview size limit exceeded, changes collapsed.

+219 −0

File added.

Preview size limit exceeded, changes collapsed.

+28 −10

File changed.

Preview size limit exceeded, changes collapsed.

Loading