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_BLUETOOTH;
20import static android.net.ConnectivityManager.TYPE_ETHERNET;
21import static android.net.ConnectivityManager.TYPE_MOBILE;
22import static android.net.ConnectivityManager.TYPE_PROXY;
23import static android.net.ConnectivityManager.TYPE_WIFI;
24import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
25import static android.net.ConnectivityManager.TYPE_WIMAX;
26import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
27import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
28import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
29import static android.net.NetworkStats.METERED_ALL;
30import static android.net.NetworkStats.METERED_NO;
31import static android.net.NetworkStats.METERED_YES;
32import static android.net.NetworkStats.ROAMING_ALL;
33import static android.net.NetworkStats.ROAMING_NO;
34import static android.net.NetworkStats.ROAMING_YES;
35import static android.net.wifi.WifiInfo.removeDoubleQuotes;
36
37import android.os.Parcel;
38import android.os.Parcelable;
39import android.util.BackupUtils;
40import android.util.Log;
41
42import com.android.internal.util.ArrayUtils;
43
44import java.io.ByteArrayOutputStream;
45import java.io.DataInputStream;
46import java.io.DataOutputStream;
47import java.io.IOException;
48import java.util.Arrays;
49import java.util.Objects;
50
51/**
52 * Predicate used to match {@link NetworkIdentity}, usually when collecting
53 * statistics. (It should probably have been named {@code NetworkPredicate}.)
54 *
55 * @hide
56 */
57public class NetworkTemplate implements Parcelable {
58    private static final String TAG = "NetworkTemplate";
59
60    /**
61     * Current Version of the Backup Serializer.
62     */
63    private static final int BACKUP_VERSION = 1;
64
65    public static final int MATCH_MOBILE = 1;
66    public static final int MATCH_WIFI = 4;
67    public static final int MATCH_ETHERNET = 5;
68    public static final int MATCH_MOBILE_WILDCARD = 6;
69    public static final int MATCH_WIFI_WILDCARD = 7;
70    public static final int MATCH_BLUETOOTH = 8;
71    public static final int MATCH_PROXY = 9;
72
73    private static boolean isKnownMatchRule(final int rule) {
74        switch (rule) {
75            case MATCH_MOBILE:
76            case MATCH_WIFI:
77            case MATCH_ETHERNET:
78            case MATCH_MOBILE_WILDCARD:
79            case MATCH_WIFI_WILDCARD:
80            case MATCH_BLUETOOTH:
81            case MATCH_PROXY:
82                return true;
83
84            default:
85                return false;
86        }
87    }
88
89    private static boolean sForceAllNetworkTypes = false;
90
91    public static void forceAllNetworkTypes() {
92        sForceAllNetworkTypes = true;
93    }
94
95    /**
96     * Template to match {@link ConnectivityManager#TYPE_MOBILE} networks with
97     * the given IMSI.
98     */
99    public static NetworkTemplate buildTemplateMobileAll(String subscriberId) {
100        return new NetworkTemplate(MATCH_MOBILE, subscriberId, null);
101    }
102
103    /**
104     * Template to match {@link ConnectivityManager#TYPE_MOBILE} networks,
105     * regardless of IMSI.
106     */
107    public static NetworkTemplate buildTemplateMobileWildcard() {
108        return new NetworkTemplate(MATCH_MOBILE_WILDCARD, null, null);
109    }
110
111    /**
112     * Template to match all {@link ConnectivityManager#TYPE_WIFI} networks,
113     * regardless of SSID.
114     */
115    public static NetworkTemplate buildTemplateWifiWildcard() {
116        return new NetworkTemplate(MATCH_WIFI_WILDCARD, null, null);
117    }
118
119    @Deprecated
120    public static NetworkTemplate buildTemplateWifi() {
121        return buildTemplateWifiWildcard();
122    }
123
124    /**
125     * Template to match {@link ConnectivityManager#TYPE_WIFI} networks with the
126     * given SSID.
127     */
128    public static NetworkTemplate buildTemplateWifi(String networkId) {
129        return new NetworkTemplate(MATCH_WIFI, null, networkId);
130    }
131
132    /**
133     * Template to combine all {@link ConnectivityManager#TYPE_ETHERNET} style
134     * networks together.
135     */
136    public static NetworkTemplate buildTemplateEthernet() {
137        return new NetworkTemplate(MATCH_ETHERNET, null, null);
138    }
139
140    /**
141     * Template to combine all {@link ConnectivityManager#TYPE_BLUETOOTH} style
142     * networks together.
143     */
144    public static NetworkTemplate buildTemplateBluetooth() {
145        return new NetworkTemplate(MATCH_BLUETOOTH, null, null);
146    }
147
148    /**
149     * Template to combine all {@link ConnectivityManager#TYPE_PROXY} style
150     * networks together.
151     */
152    public static NetworkTemplate buildTemplateProxy() {
153        return new NetworkTemplate(MATCH_PROXY, null, null);
154    }
155
156    private final int mMatchRule;
157    private final String mSubscriberId;
158
159    /**
160     * Ugh, templates are designed to target a single subscriber, but we might
161     * need to match several "merged" subscribers. These are the subscribers
162     * that should be considered to match this template.
163     * <p>
164     * Since the merge set is dynamic, it should <em>not</em> be persisted or
165     * used for determining equality.
166     */
167    private final String[] mMatchSubscriberIds;
168
169    private final String mNetworkId;
170
171    // Matches for the NetworkStats constants METERED_*, ROAMING_* and DEFAULT_NETWORK_*.
172    private final int mMetered;
173    private final int mRoaming;
174    private final int mDefaultNetwork;
175
176    public NetworkTemplate(int matchRule, String subscriberId, String networkId) {
177        this(matchRule, subscriberId, new String[] { subscriberId }, networkId);
178    }
179
180    public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
181            String networkId) {
182        this(matchRule, subscriberId, matchSubscriberIds, networkId, METERED_ALL, ROAMING_ALL,
183                DEFAULT_NETWORK_ALL);
184    }
185
186    public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
187            String networkId, int metered, int roaming, int defaultNetwork) {
188        mMatchRule = matchRule;
189        mSubscriberId = subscriberId;
190        mMatchSubscriberIds = matchSubscriberIds;
191        mNetworkId = networkId;
192        mMetered = metered;
193        mRoaming = roaming;
194        mDefaultNetwork = defaultNetwork;
195
196        if (!isKnownMatchRule(matchRule)) {
197            Log.e(TAG, "Unknown network template rule " + matchRule
198                    + " will not match any identity.");
199        }
200    }
201
202    private NetworkTemplate(Parcel in) {
203        mMatchRule = in.readInt();
204        mSubscriberId = in.readString();
205        mMatchSubscriberIds = in.createStringArray();
206        mNetworkId = in.readString();
207        mMetered = in.readInt();
208        mRoaming = in.readInt();
209        mDefaultNetwork = in.readInt();
210    }
211
212    @Override
213    public void writeToParcel(Parcel dest, int flags) {
214        dest.writeInt(mMatchRule);
215        dest.writeString(mSubscriberId);
216        dest.writeStringArray(mMatchSubscriberIds);
217        dest.writeString(mNetworkId);
218        dest.writeInt(mMetered);
219        dest.writeInt(mRoaming);
220        dest.writeInt(mDefaultNetwork);
221    }
222
223    @Override
224    public int describeContents() {
225        return 0;
226    }
227
228    @Override
229    public String toString() {
230        final StringBuilder builder = new StringBuilder("NetworkTemplate: ");
231        builder.append("matchRule=").append(getMatchRuleName(mMatchRule));
232        if (mSubscriberId != null) {
233            builder.append(", subscriberId=").append(
234                    NetworkIdentity.scrubSubscriberId(mSubscriberId));
235        }
236        if (mMatchSubscriberIds != null) {
237            builder.append(", matchSubscriberIds=").append(
238                    Arrays.toString(NetworkIdentity.scrubSubscriberId(mMatchSubscriberIds)));
239        }
240        if (mNetworkId != null) {
241            builder.append(", networkId=").append(mNetworkId);
242        }
243        if (mMetered != METERED_ALL) {
244            builder.append(", metered=").append(NetworkStats.meteredToString(mMetered));
245        }
246        if (mRoaming != ROAMING_ALL) {
247            builder.append(", roaming=").append(NetworkStats.roamingToString(mRoaming));
248        }
249        if (mDefaultNetwork != DEFAULT_NETWORK_ALL) {
250            builder.append(", defaultNetwork=").append(NetworkStats.defaultNetworkToString(
251                    mDefaultNetwork));
252        }
253        return builder.toString();
254    }
255
256    @Override
257    public int hashCode() {
258        return Objects.hash(mMatchRule, mSubscriberId, mNetworkId, mMetered, mRoaming,
259                mDefaultNetwork);
260    }
261
262    @Override
263    public boolean equals(Object obj) {
264        if (obj instanceof NetworkTemplate) {
265            final NetworkTemplate other = (NetworkTemplate) obj;
266            return mMatchRule == other.mMatchRule
267                    && Objects.equals(mSubscriberId, other.mSubscriberId)
268                    && Objects.equals(mNetworkId, other.mNetworkId)
269                    && mMetered == other.mMetered
270                    && mRoaming == other.mRoaming
271                    && mDefaultNetwork == other.mDefaultNetwork;
272        }
273        return false;
274    }
275
276    public boolean isMatchRuleMobile() {
277        switch (mMatchRule) {
278            case MATCH_MOBILE:
279            case MATCH_MOBILE_WILDCARD:
280                return true;
281            default:
282                return false;
283        }
284    }
285
286    public boolean isPersistable() {
287        switch (mMatchRule) {
288            case MATCH_MOBILE_WILDCARD:
289            case MATCH_WIFI_WILDCARD:
290                return false;
291            default:
292                return true;
293        }
294    }
295
296    public int getMatchRule() {
297        return mMatchRule;
298    }
299
300    public String getSubscriberId() {
301        return mSubscriberId;
302    }
303
304    public String getNetworkId() {
305        return mNetworkId;
306    }
307
308    /**
309     * Test if given {@link NetworkIdentity} matches this template.
310     */
311    public boolean matches(NetworkIdentity ident) {
312        if (!matchesMetered(ident)) return false;
313        if (!matchesRoaming(ident)) return false;
314        if (!matchesDefaultNetwork(ident)) return false;
315
316        switch (mMatchRule) {
317            case MATCH_MOBILE:
318                return matchesMobile(ident);
319            case MATCH_WIFI:
320                return matchesWifi(ident);
321            case MATCH_ETHERNET:
322                return matchesEthernet(ident);
323            case MATCH_MOBILE_WILDCARD:
324                return matchesMobileWildcard(ident);
325            case MATCH_WIFI_WILDCARD:
326                return matchesWifiWildcard(ident);
327            case MATCH_BLUETOOTH:
328                return matchesBluetooth(ident);
329            case MATCH_PROXY:
330                return matchesProxy(ident);
331            default:
332                // We have no idea what kind of network template we are, so we
333                // just claim not to match anything.
334                return false;
335        }
336    }
337
338    private boolean matchesMetered(NetworkIdentity ident) {
339        return (mMetered == METERED_ALL)
340            || (mMetered == METERED_YES && ident.mMetered)
341            || (mMetered == METERED_NO && !ident.mMetered);
342    }
343
344    private boolean matchesRoaming(NetworkIdentity ident) {
345        return (mRoaming == ROAMING_ALL)
346            || (mRoaming == ROAMING_YES && ident.mRoaming)
347            || (mRoaming == ROAMING_NO && !ident.mRoaming);
348    }
349
350    private boolean matchesDefaultNetwork(NetworkIdentity ident) {
351        return (mDefaultNetwork == DEFAULT_NETWORK_ALL)
352            || (mDefaultNetwork == DEFAULT_NETWORK_YES && ident.mDefaultNetwork)
353            || (mDefaultNetwork == DEFAULT_NETWORK_NO && !ident.mDefaultNetwork);
354    }
355
356    public boolean matchesSubscriberId(String subscriberId) {
357        return ArrayUtils.contains(mMatchSubscriberIds, subscriberId);
358    }
359
360    /**
361     * Check if mobile network with matching IMSI.
362     */
363    private boolean matchesMobile(NetworkIdentity ident) {
364        if (ident.mType == TYPE_WIMAX) {
365            // TODO: consider matching against WiMAX subscriber identity
366            return true;
367        } else {
368            return (sForceAllNetworkTypes || (ident.mType == TYPE_MOBILE && ident.mMetered))
369                    && !ArrayUtils.isEmpty(mMatchSubscriberIds)
370                    && ArrayUtils.contains(mMatchSubscriberIds, ident.mSubscriberId);
371        }
372    }
373
374    /**
375     * Check if matches Wi-Fi network template.
376     */
377    private boolean matchesWifi(NetworkIdentity ident) {
378        switch (ident.mType) {
379            case TYPE_WIFI:
380                return Objects.equals(
381                        removeDoubleQuotes(mNetworkId), removeDoubleQuotes(ident.mNetworkId));
382            default:
383                return false;
384        }
385    }
386
387    /**
388     * Check if matches Ethernet network template.
389     */
390    private boolean matchesEthernet(NetworkIdentity ident) {
391        if (ident.mType == TYPE_ETHERNET) {
392            return true;
393        }
394        return false;
395    }
396
397    private boolean matchesMobileWildcard(NetworkIdentity ident) {
398        if (ident.mType == TYPE_WIMAX) {
399            return true;
400        } else {
401            return sForceAllNetworkTypes || (ident.mType == TYPE_MOBILE && ident.mMetered);
402        }
403    }
404
405    private boolean matchesWifiWildcard(NetworkIdentity ident) {
406        switch (ident.mType) {
407            case TYPE_WIFI:
408            case TYPE_WIFI_P2P:
409                return true;
410            default:
411                return false;
412        }
413    }
414
415    /**
416     * Check if matches Bluetooth network template.
417     */
418    private boolean matchesBluetooth(NetworkIdentity ident) {
419        if (ident.mType == TYPE_BLUETOOTH) {
420            return true;
421        }
422        return false;
423    }
424
425    /**
426     * Check if matches Proxy network template.
427     */
428    private boolean matchesProxy(NetworkIdentity ident) {
429        return ident.mType == TYPE_PROXY;
430    }
431
432    private static String getMatchRuleName(int matchRule) {
433        switch (matchRule) {
434            case MATCH_MOBILE:
435                return "MOBILE";
436            case MATCH_WIFI:
437                return "WIFI";
438            case MATCH_ETHERNET:
439                return "ETHERNET";
440            case MATCH_MOBILE_WILDCARD:
441                return "MOBILE_WILDCARD";
442            case MATCH_WIFI_WILDCARD:
443                return "WIFI_WILDCARD";
444            case MATCH_BLUETOOTH:
445                return "BLUETOOTH";
446            case MATCH_PROXY:
447                return "PROXY";
448            default:
449                return "UNKNOWN(" + matchRule + ")";
450        }
451    }
452
453    /**
454     * Examine the given template and normalize if it refers to a "merged"
455     * mobile subscriber. We pick the "lowest" merged subscriber as the primary
456     * for key purposes, and expand the template to match all other merged
457     * subscribers.
458     * <p>
459     * For example, given an incoming template matching B, and the currently
460     * active merge set [A,B], we'd return a new template that primarily matches
461     * A, but also matches B.
462     */
463    public static NetworkTemplate normalize(NetworkTemplate template, String[] merged) {
464        if (template.isMatchRuleMobile() && ArrayUtils.contains(merged, template.mSubscriberId)) {
465            // Requested template subscriber is part of the merge group; return
466            // a template that matches all merged subscribers.
467            return new NetworkTemplate(template.mMatchRule, merged[0], merged,
468                    template.mNetworkId);
469        } else {
470            return template;
471        }
472    }
473
474    public static final Creator<NetworkTemplate> CREATOR = new Creator<NetworkTemplate>() {
475        @Override
476        public NetworkTemplate createFromParcel(Parcel in) {
477            return new NetworkTemplate(in);
478        }
479
480        @Override
481        public NetworkTemplate[] newArray(int size) {
482            return new NetworkTemplate[size];
483        }
484    };
485
486    public byte[] getBytesForBackup() throws IOException {
487        ByteArrayOutputStream baos = new ByteArrayOutputStream();
488        DataOutputStream out = new DataOutputStream(baos);
489
490        out.writeInt(BACKUP_VERSION);
491
492        out.writeInt(mMatchRule);
493        BackupUtils.writeString(out, mSubscriberId);
494        BackupUtils.writeString(out, mNetworkId);
495
496        return baos.toByteArray();
497    }
498
499    public static NetworkTemplate getNetworkTemplateFromBackup(DataInputStream in)
500            throws IOException, BackupUtils.BadVersionException {
501        int version = in.readInt();
502        if (version < 1 || version > BACKUP_VERSION) {
503            throw new BackupUtils.BadVersionException("Unknown Backup Serialization Version");
504        }
505
506        int matchRule = in.readInt();
507        String subscriberId = BackupUtils.readString(in);
508        String networkId = BackupUtils.readString(in);
509
510        if (!isKnownMatchRule(matchRule)) {
511            throw new BackupUtils.BadVersionException(
512                    "Restored network template contains unknown match rule " + matchRule);
513        }
514
515        return new NetworkTemplate(matchRule, subscriberId, networkId);
516    }
517}
518