1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.net;
18
19import static android.net.ConnectivityManager.TYPE_WIFI;
20import static android.net.ConnectivityManager.getNetworkTypeName;
21import static android.net.ConnectivityManager.isNetworkTypeMobile;
22
23import android.content.Context;
24import android.net.wifi.WifiInfo;
25import android.net.wifi.WifiManager;
26import android.os.Build;
27import android.service.NetworkIdentityProto;
28import android.telephony.TelephonyManager;
29import android.util.Slog;
30import android.util.proto.ProtoOutputStream;
31
32import java.util.Objects;
33
34/**
35 * Network definition that includes strong identity. Analogous to combining
36 * {@link NetworkInfo} and an IMSI.
37 *
38 * @hide
39 */
40public class NetworkIdentity implements Comparable<NetworkIdentity> {
41    private static final String TAG = "NetworkIdentity";
42
43    /**
44     * When enabled, combine all {@link #mSubType} together under
45     * {@link #SUBTYPE_COMBINED}.
46     *
47     * @deprecated we no longer offer to collect statistics on a per-subtype
48     *             basis; this is always disabled.
49     */
50    @Deprecated
51    public static final boolean COMBINE_SUBTYPE_ENABLED = true;
52
53    public static final int SUBTYPE_COMBINED = -1;
54
55    final int mType;
56    final int mSubType;
57    final String mSubscriberId;
58    final String mNetworkId;
59    final boolean mRoaming;
60    final boolean mMetered;
61    final boolean mDefaultNetwork;
62
63    public NetworkIdentity(
64            int type, int subType, String subscriberId, String networkId, boolean roaming,
65            boolean metered, boolean defaultNetwork) {
66        mType = type;
67        mSubType = COMBINE_SUBTYPE_ENABLED ? SUBTYPE_COMBINED : subType;
68        mSubscriberId = subscriberId;
69        mNetworkId = networkId;
70        mRoaming = roaming;
71        mMetered = metered;
72        mDefaultNetwork = defaultNetwork;
73    }
74
75    @Override
76    public int hashCode() {
77        return Objects.hash(mType, mSubType, mSubscriberId, mNetworkId, mRoaming, mMetered,
78                mDefaultNetwork);
79    }
80
81    @Override
82    public boolean equals(Object obj) {
83        if (obj instanceof NetworkIdentity) {
84            final NetworkIdentity ident = (NetworkIdentity) obj;
85            return mType == ident.mType && mSubType == ident.mSubType && mRoaming == ident.mRoaming
86                    && Objects.equals(mSubscriberId, ident.mSubscriberId)
87                    && Objects.equals(mNetworkId, ident.mNetworkId)
88                    && mMetered == ident.mMetered
89                    && mDefaultNetwork == ident.mDefaultNetwork;
90        }
91        return false;
92    }
93
94    @Override
95    public String toString() {
96        final StringBuilder builder = new StringBuilder("{");
97        builder.append("type=").append(getNetworkTypeName(mType));
98        builder.append(", subType=");
99        if (COMBINE_SUBTYPE_ENABLED) {
100            builder.append("COMBINED");
101        } else if (ConnectivityManager.isNetworkTypeMobile(mType)) {
102            builder.append(TelephonyManager.getNetworkTypeName(mSubType));
103        } else {
104            builder.append(mSubType);
105        }
106        if (mSubscriberId != null) {
107            builder.append(", subscriberId=").append(scrubSubscriberId(mSubscriberId));
108        }
109        if (mNetworkId != null) {
110            builder.append(", networkId=").append(mNetworkId);
111        }
112        if (mRoaming) {
113            builder.append(", ROAMING");
114        }
115        builder.append(", metered=").append(mMetered);
116        builder.append(", defaultNetwork=").append(mDefaultNetwork);
117        return builder.append("}").toString();
118    }
119
120    public void writeToProto(ProtoOutputStream proto, long tag) {
121        final long start = proto.start(tag);
122
123        proto.write(NetworkIdentityProto.TYPE, mType);
124
125        // Not dumping mSubType, subtypes are no longer supported.
126
127        if (mSubscriberId != null) {
128            proto.write(NetworkIdentityProto.SUBSCRIBER_ID, scrubSubscriberId(mSubscriberId));
129        }
130        proto.write(NetworkIdentityProto.NETWORK_ID, mNetworkId);
131        proto.write(NetworkIdentityProto.ROAMING, mRoaming);
132        proto.write(NetworkIdentityProto.METERED, mMetered);
133        proto.write(NetworkIdentityProto.DEFAULT_NETWORK, mDefaultNetwork);
134
135        proto.end(start);
136    }
137
138    public int getType() {
139        return mType;
140    }
141
142    public int getSubType() {
143        return mSubType;
144    }
145
146    public String getSubscriberId() {
147        return mSubscriberId;
148    }
149
150    public String getNetworkId() {
151        return mNetworkId;
152    }
153
154    public boolean getRoaming() {
155        return mRoaming;
156    }
157
158    public boolean getMetered() {
159        return mMetered;
160    }
161
162    public boolean getDefaultNetwork() {
163        return mDefaultNetwork;
164    }
165
166    /**
167     * Scrub given IMSI on production builds.
168     */
169    public static String scrubSubscriberId(String subscriberId) {
170        if (Build.IS_ENG) {
171            return subscriberId;
172        } else if (subscriberId != null) {
173            // TODO: parse this as MCC+MNC instead of hard-coding
174            return subscriberId.substring(0, Math.min(6, subscriberId.length())) + "...";
175        } else {
176            return "null";
177        }
178    }
179
180    /**
181     * Scrub given IMSI on production builds.
182     */
183    public static String[] scrubSubscriberId(String[] subscriberId) {
184        if (subscriberId == null) return null;
185        final String[] res = new String[subscriberId.length];
186        for (int i = 0; i < res.length; i++) {
187            res[i] = NetworkIdentity.scrubSubscriberId(subscriberId[i]);
188        }
189        return res;
190    }
191
192    /**
193     * Build a {@link NetworkIdentity} from the given {@link NetworkState},
194     * assuming that any mobile networks are using the current IMSI.
195     */
196    public static NetworkIdentity buildNetworkIdentity(Context context, NetworkState state,
197            boolean defaultNetwork) {
198        final int type = state.networkInfo.getType();
199        final int subType = state.networkInfo.getSubtype();
200
201        String subscriberId = null;
202        String networkId = null;
203        boolean roaming = !state.networkCapabilities.hasCapability(
204                NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
205        boolean metered = !state.networkCapabilities.hasCapability(
206                NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
207
208        if (isNetworkTypeMobile(type)) {
209            if (state.subscriberId == null) {
210                if (state.networkInfo.getState() != NetworkInfo.State.DISCONNECTED &&
211                        state.networkInfo.getState() != NetworkInfo.State.UNKNOWN) {
212                    Slog.w(TAG, "Active mobile network without subscriber! ni = "
213                            + state.networkInfo);
214                }
215            }
216
217            subscriberId = state.subscriberId;
218
219        } else if (type == TYPE_WIFI) {
220            if (state.networkId != null) {
221                networkId = state.networkId;
222            } else {
223                final WifiManager wifi = (WifiManager) context.getSystemService(
224                        Context.WIFI_SERVICE);
225                final WifiInfo info = wifi.getConnectionInfo();
226                networkId = info != null ? info.getSSID() : null;
227            }
228        }
229
230        return new NetworkIdentity(type, subType, subscriberId, networkId, roaming, metered,
231                defaultNetwork);
232    }
233
234    @Override
235    public int compareTo(NetworkIdentity another) {
236        int res = Integer.compare(mType, another.mType);
237        if (res == 0) {
238            res = Integer.compare(mSubType, another.mSubType);
239        }
240        if (res == 0 && mSubscriberId != null && another.mSubscriberId != null) {
241            res = mSubscriberId.compareTo(another.mSubscriberId);
242        }
243        if (res == 0 && mNetworkId != null && another.mNetworkId != null) {
244            res = mNetworkId.compareTo(another.mNetworkId);
245        }
246        if (res == 0) {
247            res = Boolean.compare(mRoaming, another.mRoaming);
248        }
249        if (res == 0) {
250            res = Boolean.compare(mMetered, another.mMetered);
251        }
252        if (res == 0) {
253            res = Boolean.compare(mDefaultNetwork, another.mDefaultNetwork);
254        }
255        return res;
256    }
257}
258