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

Commit 49d38d8b authored by Junyu Lai's avatar Junyu Lai Committed by Android (Google) Code Review
Browse files

Merge changes from topics "sm14-puller", "sm16-per-sub" into rvc-dev

* changes:
  Move NetworkStatsExt to a standalone file
  [SM16] Collect metrics per subscription
  [SM14] Create new puller for reporting data usage per rat
parents e7794e69 c2d3a688
Loading
Loading
Loading
Loading
+175 −56
Original line number Diff line number Diff line
@@ -24,17 +24,24 @@ import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkTemplate.NETWORK_TYPE_ALL;
import static android.net.NetworkTemplate.buildTemplateMobileWildcard;
import static android.net.NetworkTemplate.buildTemplateMobileWithRatType;
import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
import static android.net.NetworkTemplate.getAllCollapsedRatTypes;
import static android.os.Debug.getIonHeapsSizeKb;
import static android.os.Process.getUidForPid;
import static android.os.storage.VolumeInfo.TYPE_PRIVATE;
import static android.os.storage.VolumeInfo.TYPE_PUBLIC;
import static android.provider.Settings.Global.NETSTATS_UID_BUCKET_DURATION;
import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID;
import static android.util.MathUtils.abs;
import static android.util.MathUtils.constrain;

import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID;
import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_TRUNCATE_TIMESTAMP;
import static com.android.internal.util.FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__NOT_OPPORTUNISTIC;
import static com.android.internal.util.FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__OPPORTUNISTIC;
import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
@@ -108,7 +115,10 @@ import android.provider.DeviceConfig;
import android.provider.Settings;
import android.stats.storage.StorageEnums;
import android.telephony.ModemActivityInfo;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -150,6 +160,8 @@ import com.android.server.notification.NotificationManagerService;
import com.android.server.role.RoleManagerInternal;
import com.android.server.stats.pull.IonMemoryUtil.IonAllocations;
import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot;
import com.android.server.stats.pull.netstats.NetworkStatsExt;
import com.android.server.stats.pull.netstats.SubInfo;
import com.android.server.storage.DiskStatsFileLogger;
import com.android.server.storage.DiskStatsLoggingService;

@@ -175,6 +187,7 @@ import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
@@ -265,6 +278,7 @@ public class StatsPullAtomService extends SystemService {
    private StorageManager mStorageManager;
    private WifiManager mWifiManager;
    private TelephonyManager mTelephony;
    private SubscriptionManager mSubscriptionManager;

    private KernelWakelockReader mKernelWakelockReader;
    private KernelWakelockStats mTmpWakelockStats;
@@ -300,6 +314,11 @@ public class StatsPullAtomService extends SystemService {
    @NonNull
    private final List<NetworkStatsExt> mNetworkStatsBaselines = new ArrayList<>();

    // Listener for monitoring subscriptions changed event.
    private StatsSubscriptionsListener mStatsSubscriptionsListener;
    // List that store SubInfo of subscriptions that ever appeared since boot.
    private final CopyOnWriteArrayList<SubInfo> mHistoricalSubs = new CopyOnWriteArrayList<>();

    public StatsPullAtomService(Context context) {
        super(context);
        mContext = context;
@@ -327,6 +346,7 @@ public class StatsPullAtomService extends SystemService {
                    case FrameworkStatsLog.MOBILE_BYTES_TRANSFER:
                    case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG:
                    case FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED:
                    case FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER:
                        return pullDataBytesTransfer(atomTag, data);
                    case FrameworkStatsLog.BLUETOOTH_BYTES_TRANSFER:
                        return pullBluetoothBytesTransfer(atomTag, data);
@@ -479,6 +499,9 @@ public class StatsPullAtomService extends SystemService {
        mStatsManager = (StatsManager) mContext.getSystemService(Context.STATS_MANAGER);
        mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
        mTelephony = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
        mSubscriptionManager = (SubscriptionManager)
                mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
        mStatsSubscriptionsListener = new StatsSubscriptionsListener(mSubscriptionManager);
        mStorageManager = (StorageManager) mContext.getSystemService(StorageManager.class);

        // Initialize DiskIO
@@ -645,12 +668,20 @@ public class StatsPullAtomService extends SystemService {
                FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG));
        mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom(
                FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED));
        mNetworkStatsBaselines.addAll(
                collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER));

        // Listen to subscription changes to record historical subscriptions that activated before
        // pulling, this is used by {@link #pullMobileBytesTransfer}.
        mSubscriptionManager.addOnSubscriptionsChangedListener(
                BackgroundThread.getExecutor(), mStatsSubscriptionsListener);

        registerWifiBytesTransfer();
        registerWifiBytesTransferBackground();
        registerMobileBytesTransfer();
        registerMobileBytesTransferBackground();
        registerBytesTransferByTagAndMetered();
        registerDataUsageBytesTransfer();
    }

    /**
@@ -786,47 +817,12 @@ public class StatsPullAtomService extends SystemService {
        );
    }

    /**
     * A data class to store a NetworkStats object with information associated to it.
     */
    private static class NetworkStatsExt {
        @NonNull
        public final NetworkStats stats;
        public final int[] transports;
        public final boolean slicedByFgbg;
        public final boolean slicedByTag;
        public final boolean slicedByMetered;

        NetworkStatsExt(@NonNull NetworkStats stats, int[] transports, boolean slicedByFgbg) {
            this(stats, transports, slicedByFgbg, /*slicedByTag=*/false, /*slicedByMetered=*/false);
        }

        NetworkStatsExt(@NonNull NetworkStats stats, int[] transports, boolean slicedByFgbg,
                boolean slicedByTag, boolean slicedByMetered) {
            this.stats = stats;

            // Sort transports array so that we can test for equality without considering order.
            this.transports = Arrays.copyOf(transports, transports.length);
            Arrays.sort(this.transports);

            this.slicedByFgbg = slicedByFgbg;
            this.slicedByTag = slicedByTag;
            this.slicedByMetered = slicedByMetered;
        }

        public boolean hasSameSlicing(@NonNull NetworkStatsExt other) {
            return Arrays.equals(transports, other.transports) && slicedByFgbg == other.slicedByFgbg
                    && slicedByTag == other.slicedByTag && slicedByMetered == other.slicedByMetered;
        }
    }

    @NonNull
    private List<NetworkStatsExt> collectNetworkStatsSnapshotForAtom(int atomTag) {
        List<NetworkStatsExt> ret = new ArrayList<>();
        switch(atomTag) {
            case FrameworkStatsLog.WIFI_BYTES_TRANSFER: {
                final NetworkStats stats = getUidNetworkStatsSnapshot(TRANSPORT_WIFI,
                        /*includeTags=*/false);
                final NetworkStats stats = getUidNetworkStatsSnapshotForTransport(TRANSPORT_WIFI);
                if (stats != null) {
                    ret.add(new NetworkStatsExt(stats.groupedByUid(), new int[] {TRANSPORT_WIFI},
                                    /*slicedByFgbg=*/false));
@@ -834,8 +830,7 @@ public class StatsPullAtomService extends SystemService {
                break;
            }
            case FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG: {
                final NetworkStats stats = getUidNetworkStatsSnapshot(TRANSPORT_WIFI,
                        /*includeTags=*/false);
                final NetworkStats stats = getUidNetworkStatsSnapshotForTransport(TRANSPORT_WIFI);
                if (stats != null) {
                    ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
                                    new int[] {TRANSPORT_WIFI}, /*slicedByFgbg=*/true));
@@ -843,8 +838,8 @@ public class StatsPullAtomService extends SystemService {
                break;
            }
            case FrameworkStatsLog.MOBILE_BYTES_TRANSFER: {
                final NetworkStats stats = getUidNetworkStatsSnapshot(TRANSPORT_CELLULAR,
                        /*includeTags=*/false);
                final NetworkStats stats =
                        getUidNetworkStatsSnapshotForTransport(TRANSPORT_CELLULAR);
                if (stats != null) {
                    ret.add(new NetworkStatsExt(stats.groupedByUid(),
                                    new int[] {TRANSPORT_CELLULAR}, /*slicedByFgbg=*/false));
@@ -852,8 +847,8 @@ public class StatsPullAtomService extends SystemService {
                break;
            }
            case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG: {
                final NetworkStats stats = getUidNetworkStatsSnapshot(TRANSPORT_CELLULAR,
                        /*includeTags=*/false);
                final NetworkStats stats =
                        getUidNetworkStatsSnapshotForTransport(TRANSPORT_CELLULAR);
                if (stats != null) {
                    ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
                                    new int[] {TRANSPORT_CELLULAR}, /*slicedByFgbg=*/true));
@@ -861,16 +856,23 @@ public class StatsPullAtomService extends SystemService {
                break;
            }
            case FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED: {
                final NetworkStats wifiStats = getUidNetworkStatsSnapshot(TRANSPORT_WIFI,
                        /*includeTags=*/true);
                final NetworkStats cellularStats = getUidNetworkStatsSnapshot(TRANSPORT_CELLULAR,
                        /*includeTags=*/true);
                final NetworkStats wifiStats = getUidNetworkStatsSnapshotForTemplate(
                        buildTemplateWifiWildcard(), /*includeTags=*/true);
                final NetworkStats cellularStats = getUidNetworkStatsSnapshotForTemplate(
                        buildTemplateMobileWildcard(), /*includeTags=*/true);
                if (wifiStats != null && cellularStats != null) {
                    final NetworkStats stats = wifiStats.add(cellularStats);
                    ret.add(new NetworkStatsExt(sliceNetworkStatsByUidTagAndMetered(stats),
                            new int[] {TRANSPORT_WIFI, TRANSPORT_CELLULAR},
                            /*slicedByFgbg=*/false, /*slicedByTag=*/true,
                                    /*slicedByMetered=*/true));
                            /*slicedByMetered=*/true, TelephonyManager.NETWORK_TYPE_UNKNOWN,
                            /*subInfo=*/null));
                }
                break;
            }
            case FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER: {
                for (final SubInfo subInfo : mHistoricalSubs) {
                    ret.addAll(getDataUsageBytesTransferSnapshotForSub(subInfo));
                }
                break;
            }
@@ -901,7 +903,8 @@ public class StatsPullAtomService extends SystemService {
            }
            final NetworkStatsExt diff = new NetworkStatsExt(
                    item.stats.subtract(baseline.stats).removeEmptyEntries(), item.transports,
                    item.slicedByFgbg, item.slicedByTag, item.slicedByMetered);
                    item.slicedByFgbg, item.slicedByTag, item.slicedByMetered, item.ratType,
                    item.subInfo);

            // If no diff, skip.
            if (diff.stats.size() == 0) continue;
@@ -910,6 +913,9 @@ public class StatsPullAtomService extends SystemService {
                case FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED:
                    addBytesTransferByTagAndMeteredAtoms(diff, pulledData);
                    break;
                case FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER:
                    addDataUsageBytesTransferAtoms(diff, pulledData);
                    break;
                default:
                    addNetworkStats(atomTag, pulledData, diff);
            }
@@ -966,18 +972,52 @@ public class StatsPullAtomService extends SystemService {
        }
    }

    private void addDataUsageBytesTransferAtoms(@NonNull NetworkStatsExt statsExt,
            @NonNull List<StatsEvent> pulledData) {
        final NetworkStats.Entry entry = new NetworkStats.Entry(); // for recycling
        for (int i = 0; i < statsExt.stats.size(); i++) {
            statsExt.stats.getValues(i, entry);
            StatsEvent e = StatsEvent.newBuilder()
                    .setAtomId(FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER)
                    .addBooleanAnnotation(ANNOTATION_ID_TRUNCATE_TIMESTAMP, true)
                    .writeInt(entry.set)
                    .writeLong(entry.rxBytes)
                    .writeLong(entry.rxPackets)
                    .writeLong(entry.txBytes)
                    .writeLong(entry.txPackets)
                    .writeInt(statsExt.ratType)
                    // Fill information about subscription, these cannot be null since invalid data
                    // would be filtered when adding into subInfo list.
                    .writeString(statsExt.subInfo.mcc)
                    .writeString(statsExt.subInfo.mnc)
                    .writeInt(statsExt.subInfo.carrierId)
                    .writeInt(statsExt.subInfo.isOpportunistic
                            ? DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__OPPORTUNISTIC
                            : DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__NOT_OPPORTUNISTIC)
                    .build();
            pulledData.add(e);
        }
    }

    /**
     * Create a snapshot of NetworkStats since boot, but add 1 bucket duration before boot as a
     * buffer to ensure at least one full bucket will be included.
     * Note that this should be only used to calculate diff since the snapshot might contains
     * some traffic before boot.
     * Create a snapshot of NetworkStats for a given transport.
     */
    @Nullable private NetworkStats getUidNetworkStatsSnapshot(int transport, boolean includeTags) {
    @Nullable private NetworkStats getUidNetworkStatsSnapshotForTransport(int transport) {
        final NetworkTemplate template = (transport == TRANSPORT_CELLULAR)
                ? NetworkTemplate.buildTemplateMobileWithRatType(
                /*subscriptionId=*/null, NETWORK_TYPE_ALL)
                : NetworkTemplate.buildTemplateWifiWildcard();
        return getUidNetworkStatsSnapshotForTemplate(template, /*includeTags=*/false);
    }

    /**
     * Create a snapshot of NetworkStats since boot for the given template, but add 1 bucket
     * duration before boot as a buffer to ensure at least one full bucket will be included.
     * Note that this should be only used to calculate diff since the snapshot might contains
     * some traffic before boot.
     */
    @Nullable private NetworkStats getUidNetworkStatsSnapshotForTemplate(
            @NonNull NetworkTemplate template, boolean includeTags) {
        final long elapsedMillisSinceBoot = SystemClock.elapsedRealtime();
        final long currentTimeInMillis = MICROSECONDS.toMillis(SystemClock.currentTimeMicro());
        final long bucketDuration = Settings.Global.getLong(mContext.getContentResolver(),
@@ -994,6 +1034,30 @@ public class StatsPullAtomService extends SystemService {
        return null;
    }

    @NonNull private List<NetworkStatsExt> getDataUsageBytesTransferSnapshotForSub(
            @NonNull SubInfo subInfo) {
        final List<NetworkStatsExt> ret = new ArrayList<>();
        for (final int ratType : getAllCollapsedRatTypes()) {
            final NetworkTemplate template =
                    buildTemplateMobileWithRatType(subInfo.subscriberId, ratType);
            final NetworkStats stats =
                    getUidNetworkStatsSnapshotForTemplate(template, /*includeTags=*/false);
            if (stats != null) {
                ret.add(new NetworkStatsExt(sliceNetworkStatsByFgbg(stats),
                        new int[] {TRANSPORT_CELLULAR}, /*slicedByFgbg=*/true,
                        /*slicedByTag=*/false, /*slicedByMetered=*/false, ratType, subInfo));
            }
        }
        return ret;
    }

    @NonNull private NetworkStats sliceNetworkStatsByFgbg(@NonNull NetworkStats stats) {
        return sliceNetworkStats(stats,
                (newEntry, oldEntry) -> {
                    newEntry.set = oldEntry.set;
                });
    }

    @NonNull private NetworkStats sliceNetworkStatsByUidAndFgbg(@NonNull NetworkStats stats) {
        return sliceNetworkStats(stats,
                (newEntry, oldEntry) -> {
@@ -1113,6 +1177,19 @@ public class StatsPullAtomService extends SystemService {
        );
    }

    private void registerDataUsageBytesTransfer() {
        int tagId = FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER;
        PullAtomMetadata metadata = new PullAtomMetadata.Builder()
                .setAdditiveFields(new int[] {2, 3, 4, 5})
                .build();
        mStatsManager.setPullAtomCallback(
                tagId,
                metadata,
                BackgroundThread.getExecutor(),
                mStatsCallbackImpl
        );
    }

    private void registerBluetoothBytesTransfer() {
        int tagId = FrameworkStatsLog.BLUETOOTH_BYTES_TRANSFER;
        PullAtomMetadata metadata = new PullAtomMetadata.Builder()
@@ -3568,4 +3645,46 @@ public class StatsPullAtomService extends SystemService {
                    FrameworkStatsLog.CONNECTIVITY_STATE_CHANGED__STATE__DISCONNECTED);
        }
    }

    private final class StatsSubscriptionsListener
            extends SubscriptionManager.OnSubscriptionsChangedListener {
        @NonNull
        private final SubscriptionManager mSm;

        StatsSubscriptionsListener(@NonNull SubscriptionManager sm) {
            mSm = sm;
        }

        @Override
        public void onSubscriptionsChanged() {
            final List<SubscriptionInfo> currentSubs = mSm.getCompleteActiveSubscriptionInfoList();
            for (final SubscriptionInfo sub : currentSubs) {
                final SubInfo match = CollectionUtils.find(mHistoricalSubs,
                        (SubInfo it) -> it.subId == sub.getSubscriptionId());
                // SubInfo exists, ignore.
                if (match != null) continue;

                // Ignore if no valid mcc, mnc, imsi, carrierId.
                final int subId = sub.getSubscriptionId();
                final String mcc = sub.getMccString();
                final String mnc = sub.getMncString();
                final String subscriberId = mTelephony.getSubscriberId(subId);
                if (TextUtils.isEmpty(subscriberId) || TextUtils.isEmpty(mcc)
                        || TextUtils.isEmpty(mnc) || sub.getCarrierId() == UNKNOWN_CARRIER_ID) {
                    Slog.e(TAG, "subInfo of subId " + subId + " is invalid, ignored.");
                    continue;
                }

                final SubInfo subInfo = new SubInfo(subId, sub.getCarrierId(), mcc, mnc,
                        subscriberId, sub.isOpportunistic());
                Slog.i(TAG, "subId " + subId + " added into historical sub list");
                mHistoricalSubs.add(subInfo);

                // Since getting snapshot when pulling will also include data before boot,
                // query stats as baseline to prevent double count is needed.
                mNetworkStatsBaselines.addAll(getDataUsageBytesTransferSnapshotForSub(subInfo));
            }
        }
    }

}
+72 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.server.stats.pull.netstats;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.NetworkStats;
import android.telephony.TelephonyManager;

import java.util.Arrays;
import java.util.Objects;

/**
 * A data class to store a NetworkStats object with information associated to it.
 *
 * @hide
 */
public class NetworkStatsExt {
    @NonNull
    public final NetworkStats stats;
    public final int[] transports;
    public final boolean slicedByFgbg;
    public final boolean slicedByTag;
    public final boolean slicedByMetered;
    public final int ratType;
    @Nullable
    public final SubInfo subInfo;

    public NetworkStatsExt(@NonNull NetworkStats stats, int[] transports, boolean slicedByFgbg) {
        this(stats, transports, slicedByFgbg, /*slicedByTag=*/false, /*slicedByMetered=*/false,
                TelephonyManager.NETWORK_TYPE_UNKNOWN, /*subInfo=*/null);
    }

    public NetworkStatsExt(@NonNull NetworkStats stats, int[] transports, boolean slicedByFgbg,
            boolean slicedByTag, boolean slicedByMetered, int ratType,
            @Nullable SubInfo subInfo) {
        this.stats = stats;

        // Sort transports array so that we can test for equality without considering order.
        this.transports = Arrays.copyOf(transports, transports.length);
        Arrays.sort(this.transports);

        this.slicedByFgbg = slicedByFgbg;
        this.slicedByTag = slicedByTag;
        this.slicedByMetered = slicedByMetered;
        this.ratType = ratType;
        this.subInfo = subInfo;
    }

    /**
     * A helper function to compare if all fields except NetworkStats are the same.
     */
    public boolean hasSameSlicing(@NonNull NetworkStatsExt other) {
        return Arrays.equals(transports, other.transports) && slicedByFgbg == other.slicedByFgbg
                && slicedByTag == other.slicedByTag && slicedByMetered == other.slicedByMetered
                && ratType == other.ratType && Objects.equals(subInfo, other.subInfo);
    }
}
+66 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.server.stats.pull.netstats;

import android.annotation.NonNull;

import java.util.Objects;

/**
 * Information for a subscription that needed for sending NetworkStats related atoms.
 *
 * @hide
 */
public final class SubInfo {
    public final int subId;
    public final int carrierId;
    @NonNull
    public final String mcc;
    @NonNull
    public final String mnc;
    @NonNull
    public final String subscriberId;
    public final boolean isOpportunistic;

    public SubInfo(int subId, int carrierId, @NonNull String mcc, @NonNull String mnc,
            @NonNull String subscriberId, boolean isOpportunistic) {
        this.subId = subId;
        this.carrierId = carrierId;
        this.mcc = mcc;
        this.mnc = mnc;
        this.subscriberId = subscriberId;
        this.isOpportunistic = isOpportunistic;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        final SubInfo other = (SubInfo) o;
        return subId == other.subId
                && carrierId == other.carrierId
                && isOpportunistic == other.isOpportunistic
                && mcc.equals(other.mcc)
                && mnc.equals(other.mnc)
                && subscriberId.equals(other.subscriberId);
    }

    @Override
    public int hashCode() {
        return Objects.hash(subId, mcc, mnc, carrierId, subscriberId, isOpportunistic);
    }
}