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
62    public NetworkIdentity(
63            int type, int subType, String subscriberId, String networkId, boolean roaming,
64            boolean metered) {
65        mType = type;
66        mSubType = COMBINE_SUBTYPE_ENABLED ? SUBTYPE_COMBINED : subType;
67        mSubscriberId = subscriberId;
68        mNetworkId = networkId;
69        mRoaming = roaming;
70        mMetered = metered;
71    }
72
73    @Override
74    public int hashCode() {
75        return Objects.hash(mType, mSubType, mSubscriberId, mNetworkId, mRoaming, mMetered);
76    }
77
78    @Override
79    public boolean equals(Object obj) {
80        if (obj instanceof NetworkIdentity) {
81            final NetworkIdentity ident = (NetworkIdentity) obj;
82            return mType == ident.mType && mSubType == ident.mSubType && mRoaming == ident.mRoaming
83                    && Objects.equals(mSubscriberId, ident.mSubscriberId)
84                    && Objects.equals(mNetworkId, ident.mNetworkId)
85                    && mMetered == ident.mMetered;
86        }
87        return false;
88    }
89
90    @Override
91    public String toString() {
92        final StringBuilder builder = new StringBuilder("{");
93        builder.append("type=").append(getNetworkTypeName(mType));
94        builder.append(", subType=");
95        if (COMBINE_SUBTYPE_ENABLED) {
96            builder.append("COMBINED");
97        } else if (ConnectivityManager.isNetworkTypeMobile(mType)) {
98            builder.append(TelephonyManager.getNetworkTypeName(mSubType));
99        } else {
100            builder.append(mSubType);
101        }
102        if (mSubscriberId != null) {
103            builder.append(", subscriberId=").append(scrubSubscriberId(mSubscriberId));
104        }
105        if (mNetworkId != null) {
106            builder.append(", networkId=").append(mNetworkId);
107        }
108        if (mRoaming) {
109            builder.append(", ROAMING");
110        }
111        builder.append(", metered=").append(mMetered);
112        return builder.append("}").toString();
113    }
114
115    public void writeToProto(ProtoOutputStream proto, long tag) {
116        final long start = proto.start(tag);
117
118        proto.write(NetworkIdentityProto.TYPE, mType);
119
120        // Not dumping mSubType, subtypes are no longer supported.
121
122        if (mSubscriberId != null) {
123            proto.write(NetworkIdentityProto.SUBSCRIBER_ID, scrubSubscriberId(mSubscriberId));
124        }
125        proto.write(NetworkIdentityProto.NETWORK_ID, mNetworkId);
126        proto.write(NetworkIdentityProto.ROAMING, mRoaming);
127        proto.write(NetworkIdentityProto.METERED, mMetered);
128
129        proto.end(start);
130    }
131
132    public int getType() {
133        return mType;
134    }
135
136    public int getSubType() {
137        return mSubType;
138    }
139
140    public String getSubscriberId() {
141        return mSubscriberId;
142    }
143
144    public String getNetworkId() {
145        return mNetworkId;
146    }
147
148    public boolean getRoaming() {
149        return mRoaming;
150    }
151
152    public boolean getMetered() {
153        return mMetered;
154    }
155
156    /**
157     * Scrub given IMSI on production builds.
158     */
159    public static String scrubSubscriberId(String subscriberId) {
160        if ("eng".equals(Build.TYPE)) {
161            return subscriberId;
162        } else if (subscriberId != null) {
163            // TODO: parse this as MCC+MNC instead of hard-coding
164            return subscriberId.substring(0, Math.min(6, subscriberId.length())) + "...";
165        } else {
166            return "null";
167        }
168    }
169
170    /**
171     * Scrub given IMSI on production builds.
172     */
173    public static String[] scrubSubscriberId(String[] subscriberId) {
174        if (subscriberId == null) return null;
175        final String[] res = new String[subscriberId.length];
176        for (int i = 0; i < res.length; i++) {
177            res[i] = NetworkIdentity.scrubSubscriberId(subscriberId[i]);
178        }
179        return res;
180    }
181
182    /**
183     * Build a {@link NetworkIdentity} from the given {@link NetworkState},
184     * assuming that any mobile networks are using the current IMSI.
185     */
186    public static NetworkIdentity buildNetworkIdentity(Context context, NetworkState state) {
187        final int type = state.networkInfo.getType();
188        final int subType = state.networkInfo.getSubtype();
189
190        String subscriberId = null;
191        String networkId = null;
192        boolean roaming = false;
193        boolean metered = !state.networkCapabilities.hasCapability(
194                NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
195
196        if (isNetworkTypeMobile(type)) {
197            if (state.subscriberId == null) {
198                if (state.networkInfo.getState() != NetworkInfo.State.DISCONNECTED &&
199                        state.networkInfo.getState() != NetworkInfo.State.UNKNOWN) {
200                    Slog.w(TAG, "Active mobile network without subscriber! ni = "
201                            + state.networkInfo);
202                }
203            }
204
205            subscriberId = state.subscriberId;
206            roaming = state.networkInfo.isRoaming();
207
208        } else if (type == TYPE_WIFI) {
209            if (state.networkId != null) {
210                networkId = state.networkId;
211            } else {
212                final WifiManager wifi = (WifiManager) context.getSystemService(
213                        Context.WIFI_SERVICE);
214                final WifiInfo info = wifi.getConnectionInfo();
215                networkId = info != null ? info.getSSID() : null;
216            }
217        }
218
219        return new NetworkIdentity(type, subType, subscriberId, networkId, roaming, metered);
220    }
221
222    @Override
223    public int compareTo(NetworkIdentity another) {
224        int res = Integer.compare(mType, another.mType);
225        if (res == 0) {
226            res = Integer.compare(mSubType, another.mSubType);
227        }
228        if (res == 0 && mSubscriberId != null && another.mSubscriberId != null) {
229            res = mSubscriberId.compareTo(another.mSubscriberId);
230        }
231        if (res == 0 && mNetworkId != null && another.mNetworkId != null) {
232            res = mNetworkId.compareTo(another.mNetworkId);
233        }
234        if (res == 0) {
235            res = Boolean.compare(mRoaming, another.mRoaming);
236        }
237        if (res == 0) {
238            res = Boolean.compare(mMetered, another.mMetered);
239        }
240        return res;
241    }
242}
243