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

Commit f2d8cfe8 authored by Benedict Wong's avatar Benedict Wong Committed by Automerger Merge Worker
Browse files

Merge changes I3b769562,I63203188,Ia9396d66,Ifec34fad am: 20bfe66a

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1515262

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: I68f43f9808dc2fda58dc15017e8ed5e6f1b14a19
parents 641a52f9 20bfe66a
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -23,6 +23,6 @@ import android.os.ParcelUuid;
 * @hide
 */
interface IVcnManagementService {
    void setVcnConfig(in ParcelUuid subscriptionGroup, in VcnConfig config);
    void setVcnConfig(in ParcelUuid subscriptionGroup, in VcnConfig config, in String opPkgName);
    void clearVcnConfig(in ParcelUuid subscriptionGroup);
}
+37 −6
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;
@@ -45,11 +46,17 @@ import java.util.Set;
public final class VcnConfig implements Parcelable {
    @NonNull private static final String TAG = VcnConfig.class.getSimpleName();

    private static final String PACKAGE_NAME_KEY = "mPackageName";
    @NonNull private final String mPackageName;

    private static final String GATEWAY_CONNECTION_CONFIGS_KEY = "mGatewayConnectionConfigs";
    @NonNull private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs;

    private VcnConfig(@NonNull Set<VcnGatewayConnectionConfig> tunnelConfigs) {
        mGatewayConnectionConfigs = Collections.unmodifiableSet(tunnelConfigs);
    private VcnConfig(
            @NonNull String packageName,
            @NonNull Set<VcnGatewayConnectionConfig> gatewayConnectionConfigs) {
        mPackageName = packageName;
        mGatewayConnectionConfigs = Collections.unmodifiableSet(gatewayConnectionConfigs);

        validate();
    }
@@ -61,6 +68,8 @@ public final class VcnConfig implements Parcelable {
     */
    @VisibleForTesting(visibility = Visibility.PRIVATE)
    public VcnConfig(@NonNull PersistableBundle in) {
        mPackageName = in.getString(PACKAGE_NAME_KEY);

        final PersistableBundle gatewayConnectionConfigsBundle =
                in.getPersistableBundle(GATEWAY_CONNECTION_CONFIGS_KEY);
        mGatewayConnectionConfigs =
@@ -72,8 +81,19 @@ public final class VcnConfig implements Parcelable {
    }

    private void validate() {
        Objects.requireNonNull(mPackageName, "packageName was null");
        Preconditions.checkCollectionNotEmpty(
                mGatewayConnectionConfigs, "gatewayConnectionConfigs");
                mGatewayConnectionConfigs, "gatewayConnectionConfigs was empty");
    }

    /**
     * Retrieve the package name of the provisioning app.
     *
     * @hide
     */
    @NonNull
    public String getProvisioningPackageName() {
        return mPackageName;
    }

    /** Retrieves the set of configured tunnels. */
@@ -91,6 +111,8 @@ public final class VcnConfig implements Parcelable {
    public PersistableBundle toPersistableBundle() {
        final PersistableBundle result = new PersistableBundle();

        result.putString(PACKAGE_NAME_KEY, mPackageName);

        final PersistableBundle gatewayConnectionConfigsBundle =
                PersistableBundleUtils.fromList(
                        new ArrayList<>(mGatewayConnectionConfigs),
@@ -102,7 +124,7 @@ public final class VcnConfig implements Parcelable {

    @Override
    public int hashCode() {
        return Objects.hash(mGatewayConnectionConfigs);
        return Objects.hash(mPackageName, mGatewayConnectionConfigs);
    }

    @Override
@@ -112,7 +134,8 @@ public final class VcnConfig implements Parcelable {
        }

        final VcnConfig rhs = (VcnConfig) other;
        return mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs);
        return mPackageName.equals(rhs.mPackageName)
                && mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs);
    }

    // Parcelable methods
@@ -143,9 +166,17 @@ public final class VcnConfig implements Parcelable {

    /** This class is used to incrementally build {@link VcnConfig} objects. */
    public static class Builder {
        @NonNull private final String mPackageName;

        @NonNull
        private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs = new ArraySet<>();

        public Builder(@NonNull Context context) {
            Objects.requireNonNull(context, "context was null");

            mPackageName = context.getOpPackageName();
        }

        /**
         * Adds a configuration for an individual gateway connection.
         *
@@ -168,7 +199,7 @@ public final class VcnConfig implements Parcelable {
         */
        @NonNull
        public VcnConfig build() {
            return new VcnConfig(mGatewayConnectionConfigs);
            return new VcnConfig(mPackageName, mGatewayConnectionConfigs);
        }
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -101,7 +101,7 @@ public final class VcnManager {
        requireNonNull(config, "config was null");

        try {
            mService.setVcnConfig(subscriptionGroup, config);
            mService.setVcnConfig(subscriptionGroup, config, mContext.getOpPackageName());
        } catch (ServiceSpecificException e) {
            throw new IOException(e);
        } catch (RemoteException e) {
+167 −32
Original line number Diff line number Diff line
@@ -16,13 +16,15 @@

package com.android.server;

import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback;

import static java.util.Objects.requireNonNull;

import android.annotation.NonNull;
import android.app.AppOpsManager;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkProvider;
import android.net.NetworkRequest;
import android.net.vcn.IVcnManagementService;
import android.net.vcn.VcnConfig;
import android.os.Binder;
@@ -43,6 +45,10 @@ import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
import com.android.server.vcn.TelephonySubscriptionTracker;
import com.android.server.vcn.Vcn;
import com.android.server.vcn.VcnContext;
import com.android.server.vcn.VcnNetworkProvider;
import com.android.server.vcn.util.PersistableBundleUtils;

import java.io.IOException;
@@ -51,6 +57,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;

/**
 * VcnManagementService manages Virtual Carrier Network profiles and lifecycles.
@@ -115,6 +122,10 @@ public class VcnManagementService extends IVcnManagementService.Stub {
    @VisibleForTesting(visibility = Visibility.PRIVATE)
    static final String VCN_CONFIG_FILE = "/data/system/vcn/configs.xml";

    // TODO(b/176956496): Directly use CarrierServiceBindHelper.UNBIND_DELAY_MILLIS
    @VisibleForTesting(visibility = Visibility.PRIVATE)
    static final long CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS = TimeUnit.SECONDS.toMillis(30);

    /* Binder context for this service */
    @NonNull private final Context mContext;
    @NonNull private final Dependencies mDeps;
@@ -122,11 +133,23 @@ public class VcnManagementService extends IVcnManagementService.Stub {
    @NonNull private final Looper mLooper;
    @NonNull private final Handler mHandler;
    @NonNull private final VcnNetworkProvider mNetworkProvider;
    @NonNull private final TelephonySubscriptionTrackerCallback mTelephonySubscriptionTrackerCb;
    @NonNull private final TelephonySubscriptionTracker mTelephonySubscriptionTracker;
    @NonNull private final VcnContext mVcnContext;

    @GuardedBy("mLock")
    @NonNull
    private final Map<ParcelUuid, VcnConfig> mConfigs = new ArrayMap<>();

    @GuardedBy("mLock")
    @NonNull
    private final Map<ParcelUuid, Vcn> mVcns = new ArrayMap<>();

    @GuardedBy("mLock")
    @NonNull
    private TelephonySubscriptionSnapshot mLastSnapshot =
            TelephonySubscriptionSnapshot.EMPTY_SNAPSHOT;

    @NonNull private final Object mLock = new Object();

    @NonNull private final PersistableBundleUtils.LockingReadWriteHelper mConfigDiskRwHelper;
@@ -139,8 +162,12 @@ public class VcnManagementService extends IVcnManagementService.Stub {
        mLooper = mDeps.getLooper();
        mHandler = new Handler(mLooper);
        mNetworkProvider = new VcnNetworkProvider(mContext, mLooper);
        mTelephonySubscriptionTrackerCb = new VcnSubscriptionTrackerCallback();
        mTelephonySubscriptionTracker = mDeps.newTelephonySubscriptionTracker(
                mContext, mLooper, mTelephonySubscriptionTrackerCb);

        mConfigDiskRwHelper = mDeps.newPersistableBundleLockingReadWriteHelper(VCN_CONFIG_FILE);
        mVcnContext = mDeps.newVcnContext(mContext, mLooper, mNetworkProvider);

        // Run on handler to ensure I/O does not block system server startup
        mHandler.post(() -> {
@@ -174,7 +201,10 @@ public class VcnManagementService extends IVcnManagementService.Stub {
                            mConfigs.put(entry.getKey(), entry.getValue());
                        }
                    }
                    // TODO: Trigger re-evaluation of active VCNs; start/stop VCNs as needed.

                    // Re-evaluate subscriptions, and start/stop VCNs. This starts with an empty
                    // snapshot, and therefore safe even before telephony subscriptions are loaded.
                    mTelephonySubscriptionTrackerCb.onNewSnapshot(mLastSnapshot);
                }
            }
        });
@@ -203,6 +233,14 @@ public class VcnManagementService extends IVcnManagementService.Stub {
            return mHandlerThread.getLooper();
        }

        /** Creates a new VcnInstance using the provided configuration */
        public TelephonySubscriptionTracker newTelephonySubscriptionTracker(
                @NonNull Context context,
                @NonNull Looper looper,
                @NonNull TelephonySubscriptionTrackerCallback callback) {
            return new TelephonySubscriptionTracker(context, new Handler(looper), callback);
        }

        /**
         * Retrieves the caller's UID
         *
@@ -225,12 +263,29 @@ public class VcnManagementService extends IVcnManagementService.Stub {
                newPersistableBundleLockingReadWriteHelper(@NonNull String path) {
            return new PersistableBundleUtils.LockingReadWriteHelper(path);
        }

        /** Creates a new VcnContext */
        public VcnContext newVcnContext(
                @NonNull Context context,
                @NonNull Looper looper,
                @NonNull VcnNetworkProvider vcnNetworkProvider) {
            return new VcnContext(context, looper, vcnNetworkProvider);
        }

        /** Creates a new Vcn instance using the provided configuration */
        public Vcn newVcn(
                @NonNull VcnContext vcnContext,
                @NonNull ParcelUuid subscriptionGroup,
                @NonNull VcnConfig config) {
            return new Vcn(vcnContext, subscriptionGroup, config);
        }
    }

    /** Notifies the VcnManagementService that external dependencies can be set up. */
    public void systemReady() {
        mContext.getSystemService(ConnectivityManager.class)
                .registerNetworkProvider(mNetworkProvider);
        mTelephonySubscriptionTracker.register();
    }

    private void enforcePrimaryUser() {
@@ -277,27 +332,112 @@ public class VcnManagementService extends IVcnManagementService.Stub {
                "Carrier privilege required for subscription group to set VCN Config");
    }

    private class VcnSubscriptionTrackerCallback implements TelephonySubscriptionTrackerCallback {
        /**
         * Handles subscription group changes, as notified by {@link TelephonySubscriptionTracker}
         *
         * <p>Start any unstarted VCN instances
         *
         * @hide
         */
        public void onNewSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) {
            // Startup VCN instances
            synchronized (mLock) {
                mLastSnapshot = snapshot;

                // Start any VCN instances as necessary
                for (Entry<ParcelUuid, VcnConfig> entry : mConfigs.entrySet()) {
                    if (snapshot.packageHasPermissionsForSubscriptionGroup(
                            entry.getKey(), entry.getValue().getProvisioningPackageName())) {
                        if (!mVcns.containsKey(entry.getKey())) {
                            startVcnLocked(entry.getKey(), entry.getValue());
                        }

                        // Cancel any scheduled teardowns for active subscriptions
                        mHandler.removeCallbacksAndMessages(mVcns.get(entry.getKey()));
                    }
                }

                // Schedule teardown of any VCN instances that have lost carrier privileges (after a
                // delay)
                for (Entry<ParcelUuid, Vcn> entry : mVcns.entrySet()) {
                    final VcnConfig config = mConfigs.get(entry.getKey());
                    if (config == null
                            || !snapshot.packageHasPermissionsForSubscriptionGroup(
                                    entry.getKey(), config.getProvisioningPackageName())) {
                        final ParcelUuid uuidToTeardown = entry.getKey();
                        final Vcn instanceToTeardown = entry.getValue();

                        mHandler.postDelayed(() -> {
                            synchronized (mLock) {
                                // Guard against case where this is run after a old instance was
                                // torn down, and a new instance was started. Verify to ensure
                                // correct instance is torn down. This could happen as a result of a
                                // Carrier App manually removing/adding a VcnConfig.
                                if (mVcns.get(uuidToTeardown) == instanceToTeardown) {
                                    mVcns.remove(uuidToTeardown).teardownAsynchronously();
                                }
                            }
                        }, instanceToTeardown, CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
                    }
                }
            }
        }
    }

    @GuardedBy("mLock")
    private void startVcnLocked(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) {
        Slog.v(TAG, "Starting VCN config for subGrp: " + subscriptionGroup);

        // TODO(b/176939047): Support multiple VCNs active at the same time, or limit to one active
        //                    VCN.

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

    @GuardedBy("mLock")
    private void startOrUpdateVcnLocked(
            @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) {
        Slog.v(TAG, "Starting or updating VCN config for subGrp: " + subscriptionGroup);

        if (mVcns.containsKey(subscriptionGroup)) {
            mVcns.get(subscriptionGroup).updateConfig(config);
        } else {
            startVcnLocked(subscriptionGroup, config);
        }
    }

    /**
     * Sets a VCN config for a given subscription group.
     *
     * <p>Implements the IVcnManagementService Binder interface.
     */
    @Override
    public void setVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) {
    public void setVcnConfig(
            @NonNull ParcelUuid subscriptionGroup,
            @NonNull VcnConfig config,
            @NonNull String opPkgName) {
        requireNonNull(subscriptionGroup, "subscriptionGroup was null");
        requireNonNull(config, "config was null");
        requireNonNull(opPkgName, "opPkgName was null");
        if (!config.getProvisioningPackageName().equals(opPkgName)) {
            throw new IllegalArgumentException("Mismatched caller and VcnConfig creator");
        }
        Slog.v(TAG, "VCN config updated for subGrp: " + subscriptionGroup);

        mContext.getSystemService(AppOpsManager.class)
                .checkPackage(mDeps.getBinderCallingUid(), config.getProvisioningPackageName());
        enforceCallingUserAndCarrierPrivilege(subscriptionGroup);

        Binder.withCleanCallingIdentity(() -> {
            synchronized (mLock) {
                mConfigs.put(subscriptionGroup, config);
                startOrUpdateVcnLocked(subscriptionGroup, config);

            // Must be done synchronously to ensure that writes do not happen out-of-order.
                writeConfigsToDiskLocked();
            }

        // TODO: Clear Binder calling identity
        // TODO: Trigger startup as necessary
        });
    }

    /**
@@ -308,18 +448,21 @@ public class VcnManagementService extends IVcnManagementService.Stub {
    @Override
    public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) {
        requireNonNull(subscriptionGroup, "subscriptionGroup was null");
        Slog.v(TAG, "VCN config cleared for subGrp: " + subscriptionGroup);

        enforceCallingUserAndCarrierPrivilege(subscriptionGroup);

        Binder.withCleanCallingIdentity(() -> {
            synchronized (mLock) {
                mConfigs.remove(subscriptionGroup);

            // Must be done synchronously to ensure that writes do not happen out-of-order.
            writeConfigsToDiskLocked();
                if (mVcns.containsKey(subscriptionGroup)) {
                    mVcns.remove(subscriptionGroup).teardownAsynchronously();
                }

        // TODO: Clear Binder calling identity
        // TODO: Trigger teardown as necessary
                writeConfigsToDiskLocked();
            }
        });
    }

    @GuardedBy("mLock")
@@ -345,19 +488,11 @@ public class VcnManagementService extends IVcnManagementService.Stub {
        }
    }

    /**
     * Network provider for VCN networks.
     *
     * @hide
     */
    public class VcnNetworkProvider extends NetworkProvider {
        VcnNetworkProvider(Context context, Looper looper) {
            super(context, looper, VcnNetworkProvider.class.getSimpleName());
        }

        @Override
        public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) {
            // TODO: Handle network requests - Ensure VCN started, and start appropriate tunnels.
    /** Get current configuration list for testing purposes */
    @VisibleForTesting(visibility = Visibility.PRIVATE)
    public Map<ParcelUuid, Vcn> getAllVcns() {
        synchronized (mLock) {
            return Collections.unmodifiableMap(mVcns);
        }
    }
}
+52 −12
Original line number Diff line number Diff line
@@ -36,6 +36,8 @@ import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;

@@ -79,6 +81,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
    @NonNull private final TelephonySubscriptionTrackerCallback mCallback;
    @NonNull private final Dependencies mDeps;

    @NonNull private final TelephonyManager mTelephonyManager;
    @NonNull private final SubscriptionManager mSubscriptionManager;
    @NonNull private final CarrierConfigManager mCarrierConfigManager;

@@ -106,6 +109,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
        mCallback = Objects.requireNonNull(callback, "Missing callback");
        mDeps = Objects.requireNonNull(deps, "Missing deps");

        mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
        mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
        mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);

@@ -139,7 +143,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
     * so callbacks & broadcasts are all serialized on mHandler, avoiding the need for locking.
     */
    public void handleSubscriptionsChanged() {
        final Set<ParcelUuid> activeSubGroups = new ArraySet<>();
        final Map<ParcelUuid, Set<String>> privilegedPackages = new HashMap<>();
        final Map<Integer, ParcelUuid> newSubIdToGroupMap = new HashMap<>();

        final List<SubscriptionInfo> allSubs = mSubscriptionManager.getAllSubscriptionInfoList();
@@ -166,12 +170,22 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
            // group.
            if (subInfo.getSimSlotIndex() != INVALID_SIM_SLOT_INDEX
                    && mReadySubIdsBySlotId.values().contains(subInfo.getSubscriptionId())) {
                activeSubGroups.add(subInfo.getGroupUuid());
                // TODO (b/172619301): Cache based on callbacks from CarrierPrivilegesTracker

                final TelephonyManager subIdSpecificTelephonyManager =
                        mTelephonyManager.createForSubscriptionId(subInfo.getSubscriptionId());

                final ParcelUuid subGroup = subInfo.getGroupUuid();
                final Set<String> pkgs =
                        privilegedPackages.getOrDefault(subGroup, new ArraySet<>());
                pkgs.addAll(subIdSpecificTelephonyManager.getPackagesWithCarrierPrivileges());

                privilegedPackages.put(subGroup, pkgs);
            }
        }

        final TelephonySubscriptionSnapshot newSnapshot =
                new TelephonySubscriptionSnapshot(newSubIdToGroupMap, activeSubGroups);
                new TelephonySubscriptionSnapshot(newSubIdToGroupMap, privilegedPackages);

        // If snapshot was meaningfully updated, fire the callback
        if (!newSnapshot.equals(mCurrentSnapshot)) {
@@ -231,22 +245,40 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
    /** TelephonySubscriptionSnapshot is a class containing info about active subscriptions */
    public static class TelephonySubscriptionSnapshot {
        private final Map<Integer, ParcelUuid> mSubIdToGroupMap;
        private final Set<ParcelUuid> mActiveGroups;
        private final Map<ParcelUuid, Set<String>> mPrivilegedPackages;

        public static final TelephonySubscriptionSnapshot EMPTY_SNAPSHOT =
                new TelephonySubscriptionSnapshot(Collections.emptyMap(), Collections.emptyMap());

        @VisibleForTesting(visibility = Visibility.PRIVATE)
        TelephonySubscriptionSnapshot(
                @NonNull Map<Integer, ParcelUuid> subIdToGroupMap,
                @NonNull Set<ParcelUuid> activeGroups) {
            mSubIdToGroupMap = Collections.unmodifiableMap(
                    Objects.requireNonNull(subIdToGroupMap, "subIdToGroupMap was null"));
            mActiveGroups = Collections.unmodifiableSet(
                    Objects.requireNonNull(activeGroups, "activeGroups was null"));
                @NonNull Map<ParcelUuid, Set<String>> privilegedPackages) {
            Objects.requireNonNull(subIdToGroupMap, "subIdToGroupMap was null");
            Objects.requireNonNull(privilegedPackages, "privilegedPackages was null");

            mSubIdToGroupMap = Collections.unmodifiableMap(subIdToGroupMap);

            final Map<ParcelUuid, Set<String>> unmodifiableInnerSets = new ArrayMap<>();
            for (Entry<ParcelUuid, Set<String>> entry : privilegedPackages.entrySet()) {
                unmodifiableInnerSets.put(
                        entry.getKey(), Collections.unmodifiableSet(entry.getValue()));
            }
            mPrivilegedPackages = Collections.unmodifiableMap(unmodifiableInnerSets);
        }

        /** Returns the active subscription groups */
        @NonNull
        public Set<ParcelUuid> getActiveSubscriptionGroups() {
            return mActiveGroups;
            return mPrivilegedPackages.keySet();
        }

        /** Checks if the provided package is carrier privileged for the specified sub group. */
        public boolean packageHasPermissionsForSubscriptionGroup(
                @NonNull ParcelUuid subGrp, @NonNull String packageName) {
            final Set<String> privilegedPackages = mPrivilegedPackages.get(subGrp);

            return privilegedPackages != null && privilegedPackages.contains(packageName);
        }

        /** Returns the Subscription Group for a given subId. */
@@ -273,7 +305,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {

        @Override
        public int hashCode() {
            return Objects.hash(mSubIdToGroupMap, mActiveGroups);
            return Objects.hash(mSubIdToGroupMap, mPrivilegedPackages);
        }

        @Override
@@ -285,7 +317,15 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
            final TelephonySubscriptionSnapshot other = (TelephonySubscriptionSnapshot) obj;

            return mSubIdToGroupMap.equals(other.mSubIdToGroupMap)
                    && mActiveGroups.equals(other.mActiveGroups);
                    && mPrivilegedPackages.equals(other.mPrivilegedPackages);
        }

        @Override
        public String toString() {
            return "TelephonySubscriptionSnapshot{ "
                    + "mSubIdToGroupMap=" + mSubIdToGroupMap
                    + ", mPrivilegedPackages=" + mPrivilegedPackages
                    + " }";
        }
    }

Loading