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 com.android.settings.net;
18
19import static android.net.NetworkPolicy.CYCLE_NONE;
20import static android.net.NetworkPolicy.LIMIT_DISABLED;
21import static android.net.NetworkPolicy.SNOOZE_NEVER;
22import static android.net.NetworkPolicy.WARNING_DISABLED;
23import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
24import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
25import static android.net.NetworkTemplate.MATCH_WIFI;
26import static android.net.NetworkTemplate.buildTemplateMobile3gLower;
27import static android.net.NetworkTemplate.buildTemplateMobile4g;
28import static android.net.NetworkTemplate.buildTemplateMobileAll;
29import static com.android.internal.util.Preconditions.checkNotNull;
30
31import android.net.NetworkPolicy;
32import android.net.NetworkPolicyManager;
33import android.net.NetworkTemplate;
34import android.net.wifi.WifiInfo;
35import android.os.AsyncTask;
36import android.text.TextUtils;
37import android.text.format.Time;
38
39import com.android.internal.util.Objects;
40import com.google.android.collect.Lists;
41import com.google.android.collect.Sets;
42
43import java.util.ArrayList;
44import java.util.HashSet;
45
46/**
47 * Utility class to modify list of {@link NetworkPolicy}. Specifically knows
48 * about which policies can coexist. This editor offers thread safety when
49 * talking with {@link NetworkPolicyManager}.
50 */
51public class NetworkPolicyEditor {
52    // TODO: be more robust when missing policies from service
53
54    public static final boolean ENABLE_SPLIT_POLICIES = false;
55
56    private NetworkPolicyManager mPolicyManager;
57    private ArrayList<NetworkPolicy> mPolicies = Lists.newArrayList();
58
59    public NetworkPolicyEditor(NetworkPolicyManager policyManager) {
60        mPolicyManager = checkNotNull(policyManager);
61    }
62
63    public void read() {
64        final NetworkPolicy[] policies = mPolicyManager.getNetworkPolicies();
65
66        boolean modified = false;
67        mPolicies.clear();
68        for (NetworkPolicy policy : policies) {
69            // TODO: find better place to clamp these
70            if (policy.limitBytes < -1) {
71                policy.limitBytes = LIMIT_DISABLED;
72                modified = true;
73            }
74            if (policy.warningBytes < -1) {
75                policy.warningBytes = WARNING_DISABLED;
76                modified = true;
77            }
78
79            mPolicies.add(policy);
80        }
81
82        // force combine any split policies when disabled
83        if (!ENABLE_SPLIT_POLICIES) {
84            modified |= forceMobilePolicyCombined();
85        }
86
87        // when we cleaned policies above, write back changes
88        if (modified) writeAsync();
89    }
90
91    public void writeAsync() {
92        // TODO: consider making more robust by passing through service
93        final NetworkPolicy[] policies = mPolicies.toArray(new NetworkPolicy[mPolicies.size()]);
94        new AsyncTask<Void, Void, Void>() {
95            @Override
96            protected Void doInBackground(Void... params) {
97                write(policies);
98                return null;
99            }
100        }.execute();
101    }
102
103    public void write(NetworkPolicy[] policies) {
104        mPolicyManager.setNetworkPolicies(policies);
105    }
106
107    public boolean hasLimitedPolicy(NetworkTemplate template) {
108        final NetworkPolicy policy = getPolicy(template);
109        return policy != null && policy.limitBytes != LIMIT_DISABLED;
110    }
111
112    public NetworkPolicy getOrCreatePolicy(NetworkTemplate template) {
113        NetworkPolicy policy = getPolicy(template);
114        if (policy == null) {
115            policy = buildDefaultPolicy(template);
116            mPolicies.add(policy);
117        }
118        return policy;
119    }
120
121    public NetworkPolicy getPolicy(NetworkTemplate template) {
122        for (NetworkPolicy policy : mPolicies) {
123            if (policy.template.equals(template)) {
124                return policy;
125            }
126        }
127        return null;
128    }
129
130    public NetworkPolicy getPolicyMaybeUnquoted(NetworkTemplate template) {
131        NetworkPolicy policy = getPolicy(template);
132        if (policy != null) {
133            return policy;
134        } else {
135            return getPolicy(buildUnquotedNetworkTemplate(template));
136        }
137    }
138
139    @Deprecated
140    private static NetworkPolicy buildDefaultPolicy(NetworkTemplate template) {
141        // TODO: move this into framework to share with NetworkPolicyManagerService
142        final int cycleDay;
143        final String cycleTimezone;
144        final boolean metered;
145
146        if (template.getMatchRule() == MATCH_WIFI) {
147            cycleDay = CYCLE_NONE;
148            cycleTimezone = Time.TIMEZONE_UTC;
149            metered = false;
150        } else {
151            final Time time = new Time();
152            time.setToNow();
153            cycleDay = time.monthDay;
154            cycleTimezone = time.timezone;
155            metered = true;
156        }
157
158        return new NetworkPolicy(template, cycleDay, cycleTimezone, WARNING_DISABLED,
159                LIMIT_DISABLED, SNOOZE_NEVER, SNOOZE_NEVER, metered, true);
160    }
161
162    public int getPolicyCycleDay(NetworkTemplate template) {
163        return getPolicy(template).cycleDay;
164    }
165
166    public void setPolicyCycleDay(NetworkTemplate template, int cycleDay, String cycleTimezone) {
167        final NetworkPolicy policy = getOrCreatePolicy(template);
168        policy.cycleDay = cycleDay;
169        policy.cycleTimezone = cycleTimezone;
170        policy.inferred = false;
171        policy.clearSnooze();
172        writeAsync();
173    }
174
175    public long getPolicyWarningBytes(NetworkTemplate template) {
176        return getPolicy(template).warningBytes;
177    }
178
179    public void setPolicyWarningBytes(NetworkTemplate template, long warningBytes) {
180        final NetworkPolicy policy = getOrCreatePolicy(template);
181        policy.warningBytes = warningBytes;
182        policy.inferred = false;
183        policy.clearSnooze();
184        writeAsync();
185    }
186
187    public long getPolicyLimitBytes(NetworkTemplate template) {
188        return getPolicy(template).limitBytes;
189    }
190
191    public void setPolicyLimitBytes(NetworkTemplate template, long limitBytes) {
192        final NetworkPolicy policy = getOrCreatePolicy(template);
193        policy.limitBytes = limitBytes;
194        policy.inferred = false;
195        policy.clearSnooze();
196        writeAsync();
197    }
198
199    public boolean getPolicyMetered(NetworkTemplate template) {
200        NetworkPolicy policy = getPolicy(template);
201        if (policy != null) {
202            return policy.metered;
203        } else {
204            return false;
205        }
206    }
207
208    public void setPolicyMetered(NetworkTemplate template, boolean metered) {
209        boolean modified = false;
210
211        NetworkPolicy policy = getPolicy(template);
212        if (metered) {
213            if (policy == null) {
214                policy = buildDefaultPolicy(template);
215                policy.metered = true;
216                policy.inferred = false;
217                mPolicies.add(policy);
218                modified = true;
219            } else if (!policy.metered) {
220                policy.metered = true;
221                policy.inferred = false;
222                modified = true;
223            }
224
225        } else {
226            if (policy == null) {
227                // ignore when policy doesn't exist
228            } else if (policy.metered) {
229                policy.metered = false;
230                policy.inferred = false;
231                modified = true;
232            }
233        }
234
235        // Remove legacy unquoted policies while we're here
236        final NetworkTemplate unquoted = buildUnquotedNetworkTemplate(template);
237        final NetworkPolicy unquotedPolicy = getPolicy(unquoted);
238        if (unquotedPolicy != null) {
239            mPolicies.remove(unquotedPolicy);
240            modified = true;
241        }
242
243        if (modified) writeAsync();
244    }
245
246    /**
247     * Remove any split {@link NetworkPolicy}.
248     */
249    private boolean forceMobilePolicyCombined() {
250        final HashSet<String> subscriberIds = Sets.newHashSet();
251        for (NetworkPolicy policy : mPolicies) {
252            subscriberIds.add(policy.template.getSubscriberId());
253        }
254
255        boolean modified = false;
256        for (String subscriberId : subscriberIds) {
257            modified |= setMobilePolicySplitInternal(subscriberId, false);
258        }
259        return modified;
260    }
261
262    @Deprecated
263    public boolean isMobilePolicySplit(String subscriberId) {
264        boolean has3g = false;
265        boolean has4g = false;
266        for (NetworkPolicy policy : mPolicies) {
267            final NetworkTemplate template = policy.template;
268            if (Objects.equal(subscriberId, template.getSubscriberId())) {
269                switch (template.getMatchRule()) {
270                    case MATCH_MOBILE_3G_LOWER:
271                        has3g = true;
272                        break;
273                    case MATCH_MOBILE_4G:
274                        has4g = true;
275                        break;
276                }
277            }
278        }
279        return has3g && has4g;
280    }
281
282    @Deprecated
283    public void setMobilePolicySplit(String subscriberId, boolean split) {
284        if (setMobilePolicySplitInternal(subscriberId, split)) {
285            writeAsync();
286        }
287    }
288
289    /**
290     * Mutate {@link NetworkPolicy} for given subscriber, combining or splitting
291     * the policy as requested.
292     *
293     * @return {@code true} when any {@link NetworkPolicy} was mutated.
294     */
295    @Deprecated
296    private boolean setMobilePolicySplitInternal(String subscriberId, boolean split) {
297        final boolean beforeSplit = isMobilePolicySplit(subscriberId);
298
299        final NetworkTemplate template3g = buildTemplateMobile3gLower(subscriberId);
300        final NetworkTemplate template4g = buildTemplateMobile4g(subscriberId);
301        final NetworkTemplate templateAll = buildTemplateMobileAll(subscriberId);
302
303        if (split == beforeSplit) {
304            // already in requested state; skip
305            return false;
306
307        } else if (beforeSplit && !split) {
308            // combine, picking most restrictive policy
309            final NetworkPolicy policy3g = getPolicy(template3g);
310            final NetworkPolicy policy4g = getPolicy(template4g);
311
312            final NetworkPolicy restrictive = policy3g.compareTo(policy4g) < 0 ? policy3g
313                    : policy4g;
314            mPolicies.remove(policy3g);
315            mPolicies.remove(policy4g);
316            mPolicies.add(new NetworkPolicy(templateAll, restrictive.cycleDay,
317                    restrictive.cycleTimezone, restrictive.warningBytes, restrictive.limitBytes,
318                    SNOOZE_NEVER, SNOOZE_NEVER, restrictive.metered, restrictive.inferred));
319            return true;
320
321        } else if (!beforeSplit && split) {
322            // duplicate existing policy into two rules
323            final NetworkPolicy policyAll = getPolicy(templateAll);
324            mPolicies.remove(policyAll);
325            mPolicies.add(new NetworkPolicy(template3g, policyAll.cycleDay, policyAll.cycleTimezone,
326                    policyAll.warningBytes, policyAll.limitBytes, SNOOZE_NEVER, SNOOZE_NEVER,
327                    policyAll.metered, policyAll.inferred));
328            mPolicies.add(new NetworkPolicy(template4g, policyAll.cycleDay, policyAll.cycleTimezone,
329                    policyAll.warningBytes, policyAll.limitBytes, SNOOZE_NEVER, SNOOZE_NEVER,
330                    policyAll.metered, policyAll.inferred));
331            return true;
332        } else {
333            return false;
334        }
335    }
336
337    /**
338     * Build a revised {@link NetworkTemplate} that matches the same rule, but
339     * with an unquoted {@link NetworkTemplate#getNetworkId()}. Used to work
340     * around legacy bugs.
341     */
342    private static NetworkTemplate buildUnquotedNetworkTemplate(NetworkTemplate template) {
343        if (template == null) return null;
344        final String networkId = template.getNetworkId();
345        final String strippedNetworkId = WifiInfo.removeDoubleQuotes(networkId);
346        if (!TextUtils.equals(strippedNetworkId, networkId)) {
347            return new NetworkTemplate(
348                    template.getMatchRule(), template.getSubscriberId(), strippedNetworkId);
349        } else {
350            return null;
351        }
352    }
353}
354