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

Commit f14145e3 authored by Cody Kesting's avatar Cody Kesting
Browse files

Define VcnStatusCallback register/unregister.

This CL defines VcnStatusCallbacks, which are callbacks used to register
for status updates to a specific subscription group. These Callbacks may
be registered with VcnManager at any time, but will only be invoked for
the specifies subscription group and only if the registering app has
carrier privileges for that subscription.

Bug: 163433613
Test: atest FrameworksVcnTests
Change-Id: Iefd284ae2d09676d195e2a12bf660be3596da59b
parent 30d9bfa0
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.net.vcn;

import android.net.LinkProperties;
import android.net.NetworkCapabilities;
import android.net.vcn.IVcnStatusCallback;
import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
import android.net.vcn.VcnConfig;
import android.net.vcn.VcnUnderlyingNetworkPolicy;
@@ -33,4 +34,7 @@ interface IVcnManagementService {
    void addVcnUnderlyingNetworkPolicyListener(in IVcnUnderlyingNetworkPolicyListener listener);
    void removeVcnUnderlyingNetworkPolicyListener(in IVcnUnderlyingNetworkPolicyListener listener);
    VcnUnderlyingNetworkPolicy getUnderlyingNetworkPolicy(in NetworkCapabilities nc, in LinkProperties lp);

    void registerVcnStatusCallback(in ParcelUuid subscriptionGroup, in IVcnStatusCallback callback, in String opPkgName);
    void unregisterVcnStatusCallback(in IVcnStatusCallback callback);
}
+22 −0
Original line number Diff line number Diff line
/*
 * Copyright 2021, 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.net.vcn;

/** @hide */
interface IVcnStatusCallback {
    void onEnteredSafeMode();
}
 No newline at end of file
+119 −1
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.annotation.SystemService;
import android.content.Context;
import android.net.LinkProperties;
import android.net.NetworkCapabilities;
import android.os.Binder;
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
@@ -267,6 +268,100 @@ public class VcnManager {
        }
    }

    // TODO: make VcnStatusCallback @SystemApi
    /**
     * VcnStatusCallback is the interface for Carrier apps to receive updates for their VCNs.
     *
     * <p>VcnStatusCallbacks may be registered before {@link VcnConfig}s are provided for a
     * subscription group.
     *
     * @hide
     */
    public abstract static class VcnStatusCallback {
        private VcnStatusCallbackBinder mCbBinder;

        /**
         * Invoked when the VCN for this Callback's subscription group enters safe mode.
         *
         * <p>A VCN will be put into safe mode if any of the gateway connections were unable to
         * establish a connection within a system-determined timeout (while underlying networks were
         * available).
         *
         * <p>A VCN-configuring app may opt to exit safe mode by (re)setting the VCN configuration
         * via {@link #setVcnConfig(ParcelUuid, VcnConfig)}.
         */
        public abstract void onEnteredSafeMode();
    }

    /**
     * Registers the given callback to receive status updates for the specified subscription.
     *
     * <p>Callbacks can be registered for a subscription before {@link VcnConfig}s are set for it.
     *
     * <p>A {@link VcnStatusCallback} may only be registered for one subscription at a time. {@link
     * VcnStatusCallback}s may be reused once unregistered.
     *
     * <p>A {@link VcnStatusCallback} will only be invoked if the registering package has carrier
     * privileges for the specified subscription at the time of invocation.
     *
     * @param subscriptionGroup The subscription group to match for callbacks
     * @param executor The {@link Executor} to be used for invoking callbacks
     * @param callback The VcnStatusCallback to be registered
     * @throws IllegalStateException if callback is currently registered with VcnManager
     * @hide
     */
    public void registerVcnStatusCallback(
            @NonNull ParcelUuid subscriptionGroup,
            @NonNull Executor executor,
            @NonNull VcnStatusCallback callback) {
        requireNonNull(subscriptionGroup, "subscriptionGroup must not be null");
        requireNonNull(executor, "executor must not be null");
        requireNonNull(callback, "callback must not be null");

        synchronized (callback) {
            if (callback.mCbBinder != null) {
                throw new IllegalStateException("callback is already registered with VcnManager");
            }
            callback.mCbBinder = new VcnStatusCallbackBinder(executor, callback);

            try {
                mService.registerVcnStatusCallback(
                        subscriptionGroup, callback.mCbBinder, mContext.getOpPackageName());
            } catch (RemoteException e) {
                callback.mCbBinder = null;
                throw e.rethrowFromSystemServer();
            }
        }
    }

    /**
     * Unregisters the given callback.
     *
     * <p>Once unregistered, the callback will stop receiving status updates for the subscription it
     * was registered with.
     *
     * @param callback The callback to be unregistered
     * @hide
     */
    public void unregisterVcnStatusCallback(@NonNull VcnStatusCallback callback) {
        requireNonNull(callback, "callback must not be null");

        synchronized (callback) {
            if (callback.mCbBinder == null) {
                // no Binder attached to this callback, so it's not currently registered
                return;
            }

            try {
                mService.unregisterVcnStatusCallback(callback.mCbBinder);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            } finally {
                callback.mCbBinder = null;
            }
        }
    }

    /**
     * Binder wrapper for added VcnUnderlyingNetworkPolicyListeners to receive signals from System
     * Server.
@@ -286,7 +381,30 @@ public class VcnManager {

        @Override
        public void onPolicyChanged() {
            mExecutor.execute(() -> mListener.onPolicyChanged());
            Binder.withCleanCallingIdentity(
                    () -> mExecutor.execute(() -> mListener.onPolicyChanged()));
        }
    }

    /**
     * Binder wrapper for VcnStatusCallbacks to receive signals from VcnManagementService.
     *
     * @hide
     */
    private class VcnStatusCallbackBinder extends IVcnStatusCallback.Stub {
        @NonNull private final Executor mExecutor;
        @NonNull private final VcnStatusCallback mCallback;

        private VcnStatusCallbackBinder(
                @NonNull Executor executor, @NonNull VcnStatusCallback callback) {
            mExecutor = executor;
            mCallback = callback;
        }

        @Override
        public void onEnteredSafeMode() {
            Binder.withCleanCallingIdentity(
                    () -> mExecutor.execute(() -> mCallback.onEnteredSafeMode()));
        }
    }
}
+114 −13
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import android.net.LinkProperties;
import android.net.NetworkCapabilities;
import android.net.TelephonyNetworkSpecifier;
import android.net.vcn.IVcnManagementService;
import android.net.vcn.IVcnStatusCallback;
import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
import android.net.vcn.VcnConfig;
import android.net.vcn.VcnUnderlyingNetworkPolicy;
@@ -124,6 +125,7 @@ import java.util.concurrent.TimeUnit;
 *
 * @hide
 */
// TODO(b/180451994): ensure all incoming + outgoing calls have a cleared calling identity
public class VcnManagementService extends IVcnManagementService.Stub {
    @NonNull private static final String TAG = VcnManagementService.class.getSimpleName();

@@ -169,6 +171,10 @@ public class VcnManagementService extends IVcnManagementService.Stub {
    private final Map<IBinder, PolicyListenerBinderDeath> mRegisteredPolicyListeners =
            new ArrayMap<>();

    @GuardedBy("mLock")
    @NonNull
    private final Map<IBinder, VcnStatusCallbackInfo> mRegisteredStatusCallbacks = new ArrayMap<>();

    @VisibleForTesting(visibility = Visibility.PRIVATE)
    VcnManagementService(@NonNull Context context, @NonNull Dependencies deps) {
        mContext = requireNonNull(context, "Missing context");
@@ -293,8 +299,8 @@ public class VcnManagementService extends IVcnManagementService.Stub {
                @NonNull ParcelUuid subscriptionGroup,
                @NonNull VcnConfig config,
                @NonNull TelephonySubscriptionSnapshot snapshot,
                @NonNull VcnSafemodeCallback safemodeCallback) {
            return new Vcn(vcnContext, subscriptionGroup, config, snapshot, safemodeCallback);
                @NonNull VcnSafeModeCallback safeModeCallback) {
            return new Vcn(vcnContext, subscriptionGroup, config, snapshot, safeModeCallback);
        }

        /** Gets the subId indicated by the given {@link WifiInfo}. */
@@ -440,12 +446,12 @@ public class VcnManagementService extends IVcnManagementService.Stub {
        // TODO(b/176939047): Support multiple VCNs active at the same time, or limit to one active
        //                    VCN.

        final VcnSafemodeCallbackImpl safemodeCallback =
                new VcnSafemodeCallbackImpl(subscriptionGroup);
        final VcnSafeModeCallbackImpl safeModeCallback =
                new VcnSafeModeCallbackImpl(subscriptionGroup);

        final Vcn newInstance =
                mDeps.newVcn(
                        mVcnContext, subscriptionGroup, config, mLastSnapshot, safemodeCallback);
                        mVcnContext, subscriptionGroup, config, mLastSnapshot, safeModeCallback);
        mVcns.put(subscriptionGroup, newInstance);

        // Now that a new VCN has started, notify all registered listeners to refresh their
@@ -551,6 +557,14 @@ public class VcnManagementService extends IVcnManagementService.Stub {
        }
    }

    /** Get current VcnStatusCallbacks for testing purposes. */
    @VisibleForTesting(visibility = Visibility.PRIVATE)
    public Map<IBinder, VcnStatusCallbackInfo> getAllStatusCallbacks() {
        synchronized (mLock) {
            return Collections.unmodifiableMap(mRegisteredStatusCallbacks);
        }
    }

    /** Binder death recipient used to remove a registered policy listener. */
    private class PolicyListenerBinderDeath implements Binder.DeathRecipient {
        @NonNull private final IVcnUnderlyingNetworkPolicyListener mListener;
@@ -672,22 +686,109 @@ public class VcnManagementService extends IVcnManagementService.Stub {
        return new VcnUnderlyingNetworkPolicy(false /* isTearDownRequested */, networkCapabilities);
    }

    /** Callback for signalling when a Vcn has entered Safemode. */
    public interface VcnSafemodeCallback {
        /** Called by a Vcn to signal that it has entered Safemode. */
        void onEnteredSafemode();
    /** Binder death recipient used to remove registered VcnStatusCallbacks. */
    @VisibleForTesting(visibility = Visibility.PRIVATE)
    class VcnStatusCallbackInfo implements Binder.DeathRecipient {
        @NonNull final ParcelUuid mSubGroup;
        @NonNull final IVcnStatusCallback mCallback;
        @NonNull final String mPkgName;
        final int mUid;

        private VcnStatusCallbackInfo(
                @NonNull ParcelUuid subGroup,
                @NonNull IVcnStatusCallback callback,
                @NonNull String pkgName,
                int uid) {
            mSubGroup = subGroup;
            mCallback = callback;
            mPkgName = pkgName;
            mUid = uid;
        }

        @Override
        public void binderDied() {
            Log.e(TAG, "app died without unregistering VcnStatusCallback");
            unregisterVcnStatusCallback(mCallback);
        }
    }

    /** Registers the provided callback for receiving VCN status updates. */
    @Override
    public void registerVcnStatusCallback(
            @NonNull ParcelUuid subGroup,
            @NonNull IVcnStatusCallback callback,
            @NonNull String opPkgName) {
        final int callingUid = mDeps.getBinderCallingUid();
        final long identity = Binder.clearCallingIdentity();
        try {
            requireNonNull(subGroup, "subGroup must not be null");
            requireNonNull(callback, "callback must not be null");
            requireNonNull(opPkgName, "opPkgName must not be null");

            mContext.getSystemService(AppOpsManager.class).checkPackage(callingUid, opPkgName);

            final IBinder cbBinder = callback.asBinder();
            final VcnStatusCallbackInfo cbInfo =
                    new VcnStatusCallbackInfo(
                            subGroup, callback, opPkgName, mDeps.getBinderCallingUid());

            try {
                cbBinder.linkToDeath(cbInfo, 0 /* flags */);
            } catch (RemoteException e) {
                // Remote binder already died - don't add to mRegisteredStatusCallbacks and exit
                return;
            }

            synchronized (mLock) {
                if (mRegisteredStatusCallbacks.containsKey(cbBinder)) {
                    throw new IllegalStateException(
                            "Attempting to register a callback that is already in use");
                }

                mRegisteredStatusCallbacks.put(cbBinder, cbInfo);
            }
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    /** Unregisters the provided callback from receiving future VCN status updates. */
    @Override
    public void unregisterVcnStatusCallback(@NonNull IVcnStatusCallback callback) {
        final long identity = Binder.clearCallingIdentity();
        try {
            requireNonNull(callback, "callback must not be null");

            final IBinder cbBinder = callback.asBinder();
            synchronized (mLock) {
                VcnStatusCallbackInfo cbInfo = mRegisteredStatusCallbacks.remove(cbBinder);

                if (cbInfo != null) {
                    cbBinder.unlinkToDeath(cbInfo, 0 /* flags */);
                }
            }
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    // TODO(b/180452282): Make name more generic and implement directly with VcnManagementService
    /** Callback for signalling when a Vcn has entered safe mode. */
    public interface VcnSafeModeCallback {
        /** Called by a Vcn to signal that it has entered safe mode. */
        void onEnteredSafeMode();
    }

    /** VcnSafemodeCallback is used by Vcns to notify VcnManagementService on entering Safemode. */
    private class VcnSafemodeCallbackImpl implements VcnSafemodeCallback {
    /** VcnSafeModeCallback is used by Vcns to notify VcnManagementService on entering safe mode. */
    private class VcnSafeModeCallbackImpl implements VcnSafeModeCallback {
        @NonNull private final ParcelUuid mSubGroup;

        private VcnSafemodeCallbackImpl(@NonNull final ParcelUuid subGroup) {
        private VcnSafeModeCallbackImpl(@NonNull final ParcelUuid subGroup) {
            mSubGroup = Objects.requireNonNull(subGroup, "Missing subGroup");
        }

        @Override
        public void onEnteredSafemode() {
        public void onEnteredSafeMode() {
            synchronized (mLock) {
                // Ignore if this subscription group doesn't exist anymore
                if (!mVcns.containsKey(mSubGroup)) {
+8 −8
Original line number Diff line number Diff line
@@ -30,7 +30,7 @@ import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
import com.android.server.VcnManagementService.VcnSafemodeCallback;
import com.android.server.VcnManagementService.VcnSafeModeCallback;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;

import java.util.Collections;
@@ -97,7 +97,7 @@ public class Vcn extends Handler {
    @NonNull private final ParcelUuid mSubscriptionGroup;
    @NonNull private final Dependencies mDeps;
    @NonNull private final VcnNetworkRequestListener mRequestListener;
    @NonNull private final VcnSafemodeCallback mVcnSafemodeCallback;
    @NonNull private final VcnSafeModeCallback mVcnSafeModeCallback;

    @NonNull
    private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> mVcnGatewayConnections =
@@ -125,13 +125,13 @@ public class Vcn extends Handler {
            @NonNull ParcelUuid subscriptionGroup,
            @NonNull VcnConfig config,
            @NonNull TelephonySubscriptionSnapshot snapshot,
            @NonNull VcnSafemodeCallback vcnSafemodeCallback) {
            @NonNull VcnSafeModeCallback vcnSafeModeCallback) {
        this(
                vcnContext,
                subscriptionGroup,
                config,
                snapshot,
                vcnSafemodeCallback,
                vcnSafeModeCallback,
                new Dependencies());
    }

@@ -141,13 +141,13 @@ public class Vcn extends Handler {
            @NonNull ParcelUuid subscriptionGroup,
            @NonNull VcnConfig config,
            @NonNull TelephonySubscriptionSnapshot snapshot,
            @NonNull VcnSafemodeCallback vcnSafemodeCallback,
            @NonNull VcnSafeModeCallback vcnSafeModeCallback,
            @NonNull Dependencies deps) {
        super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
        mVcnContext = vcnContext;
        mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
        mVcnSafemodeCallback =
                Objects.requireNonNull(vcnSafemodeCallback, "Missing vcnSafemodeCallback");
        mVcnSafeModeCallback =
                Objects.requireNonNull(vcnSafeModeCallback, "Missing vcnSafeModeCallback");
        mDeps = Objects.requireNonNull(deps, "Missing deps");
        mRequestListener = new VcnNetworkRequestListener();

@@ -246,7 +246,7 @@ public class Vcn extends Handler {
    private void handleEnterSafemode() {
        handleTeardown();

        mVcnSafemodeCallback.onEnteredSafemode();
        mVcnSafeModeCallback.onEnteredSafeMode();
    }

    private void handleNetworkRequested(
Loading