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.telephony.TelephonyManager;
28import android.util.Slog;
29
30import java.util.Objects;
31
32/**
33 * Network definition that includes strong identity. Analogous to combining
34 * {@link NetworkInfo} and an IMSI.
35 *
36 * @hide
37 */
38public class NetworkIdentity implements Comparable<NetworkIdentity> {
39    private static final String TAG = "NetworkIdentity";
40
41    /**
42     * When enabled, combine all {@link #mSubType} together under
43     * {@link #SUBTYPE_COMBINED}.
44     *
45     * @deprecated we no longer offer to collect statistics on a per-subtype
46     *             basis; this is always disabled.
47     */
48    @Deprecated
49    public static final boolean COMBINE_SUBTYPE_ENABLED = true;
50
51    public static final int SUBTYPE_COMBINED = -1;
52
53    final int mType;
54    final int mSubType;
55    final String mSubscriberId;
56    final String mNetworkId;
57    final boolean mRoaming;
58    final boolean mMetered;
59
60    public NetworkIdentity(
61            int type, int subType, String subscriberId, String networkId, boolean roaming,
62            boolean metered) {
63        mType = type;
64        mSubType = COMBINE_SUBTYPE_ENABLED ? SUBTYPE_COMBINED : subType;
65        mSubscriberId = subscriberId;
66        mNetworkId = networkId;
67        mRoaming = roaming;
68        mMetered = metered;
69    }
70
71    @Override
72    public int hashCode() {
73        return Objects.hash(mType, mSubType, mSubscriberId, mNetworkId, mRoaming, mMetered);
74    }
75
76    @Override
77    public boolean equals(Object obj) {
78        if (obj instanceof NetworkIdentity) {
79            final NetworkIdentity ident = (NetworkIdentity) obj;
80            return mType == ident.mType && mSubType == ident.mSubType && mRoaming == ident.mRoaming
81                    && Objects.equals(mSubscriberId, ident.mSubscriberId)
82                    && Objects.equals(mNetworkId, ident.mNetworkId)
83                    && mMetered == ident.mMetered;
84        }
85        return false;
86    }
87
88    @Override
89    public String toString() {
90        final StringBuilder builder = new StringBuilder("{");
91        builder.append("type=").append(getNetworkTypeName(mType));
92        builder.append(", subType=");
93        if (COMBINE_SUBTYPE_ENABLED) {
94            builder.append("COMBINED");
95        } else if (ConnectivityManager.isNetworkTypeMobile(mType)) {
96            builder.append(TelephonyManager.getNetworkTypeName(mSubType));
97        } else {
98            builder.append(mSubType);
99        }
100        if (mSubscriberId != null) {
101            builder.append(", subscriberId=").append(scrubSubscriberId(mSubscriberId));
102        }
103        if (mNetworkId != null) {
104            builder.append(", networkId=").append(mNetworkId);
105        }
106        if (mRoaming) {
107            builder.append(", ROAMING");
108        }
109        builder.append(", metered=").append(mMetered);
110        return builder.append("}").toString();
111    }
112
113    public int getType() {
114        return mType;
115    }
116
117    public int getSubType() {
118        return mSubType;
119    }
120
121    public String getSubscriberId() {
122        return mSubscriberId;
123    }
124
125    public String getNetworkId() {
126        return mNetworkId;
127    }
128
129    public boolean getRoaming() {
130        return mRoaming;
131    }
132
133    public boolean getMetered() {
134        return mMetered;
135    }
136
137    /**
138     * Scrub given IMSI on production builds.
139     */
140    public static String scrubSubscriberId(String subscriberId) {
141        if ("eng".equals(Build.TYPE)) {
142            return subscriberId;
143        } else if (subscriberId != null) {
144            // TODO: parse this as MCC+MNC instead of hard-coding
145            return subscriberId.substring(0, Math.min(6, subscriberId.length())) + "...";
146        } else {
147            return "null";
148        }
149    }
150
151    /**
152     * Scrub given IMSI on production builds.
153     */
154    public static String[] scrubSubscriberId(String[] subscriberId) {
155        if (subscriberId == null) return null;
156        final String[] res = new String[subscriberId.length];
157        for (int i = 0; i < res.length; i++) {
158            res[i] = NetworkIdentity.scrubSubscriberId(subscriberId[i]);
159        }
160        return res;
161    }
162
163    /**
164     * Build a {@link NetworkIdentity} from the given {@link NetworkState},
165     * assuming that any mobile networks are using the current IMSI.
166     */
167    public static NetworkIdentity buildNetworkIdentity(Context context, NetworkState state) {
168        final int type = state.networkInfo.getType();
169        final int subType = state.networkInfo.getSubtype();
170
171        String subscriberId = null;
172        String networkId = null;
173        boolean roaming = false;
174        boolean metered = false;
175
176        if (isNetworkTypeMobile(type)) {
177            if (state.subscriberId == null) {
178                if (state.networkInfo.getState() != NetworkInfo.State.DISCONNECTED &&
179                        state.networkInfo.getState() != NetworkInfo.State.UNKNOWN) {
180                    Slog.w(TAG, "Active mobile network without subscriber! ni = "
181                            + state.networkInfo);
182                }
183            }
184
185            subscriberId = state.subscriberId;
186            roaming = state.networkInfo.isRoaming();
187
188            metered = !state.networkCapabilities.hasCapability(
189                    NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
190
191        } else if (type == TYPE_WIFI) {
192            if (state.networkId != null) {
193                networkId = state.networkId;
194            } else {
195                final WifiManager wifi = (WifiManager) context.getSystemService(
196                        Context.WIFI_SERVICE);
197                final WifiInfo info = wifi.getConnectionInfo();
198                networkId = info != null ? info.getSSID() : null;
199            }
200        }
201
202        return new NetworkIdentity(type, subType, subscriberId, networkId, roaming, metered);
203    }
204
205    @Override
206    public int compareTo(NetworkIdentity another) {
207        int res = Integer.compare(mType, another.mType);
208        if (res == 0) {
209            res = Integer.compare(mSubType, another.mSubType);
210        }
211        if (res == 0 && mSubscriberId != null && another.mSubscriberId != null) {
212            res = mSubscriberId.compareTo(another.mSubscriberId);
213        }
214        if (res == 0 && mNetworkId != null && another.mNetworkId != null) {
215            res = mNetworkId.compareTo(another.mNetworkId);
216        }
217        if (res == 0) {
218            res = Boolean.compare(mRoaming, another.mRoaming);
219        }
220        if (res == 0) {
221            res = Boolean.compare(mMetered, another.mMetered);
222        }
223        return res;
224    }
225}
226