Modifier to combine/split mobile network policy.
Create NetworkPolicyModifier which knows about which mobile network
policies can coexist. Settings UI uses this modifier to drive UI and
persist policies.
Change-Id: Ib3f3841b0a74c14eefb99209dd644a2e7b7e525d
diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java
index e27227f..44a86df 100644
--- a/src/com/android/settings/DataUsageSummary.java
+++ b/src/com/android/settings/DataUsageSummary.java
@@ -16,6 +16,7 @@
package com.android.settings;
+import static android.net.NetworkPolicy.LIMIT_DISABLED;
import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
import static android.net.NetworkPolicyManager.computeNextCycleBoundary;
import static android.net.TrafficStats.TEMPLATE_MOBILE_3G_LOWER;
@@ -37,6 +38,8 @@
import android.os.ServiceManager;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
+import android.preference.SwitchPreference;
+import android.telephony.TelephonyManager;
import android.text.format.DateUtils;
import android.text.format.Formatter;
import android.text.format.Time;
@@ -64,6 +67,7 @@
import android.widget.TabWidget;
import android.widget.TextView;
+import com.android.settings.net.NetworkPolicyModifier;
import com.android.settings.widget.DataUsageChartView;
import com.android.settings.widget.DataUsageChartView.DataUsageChartListener;
import com.google.android.collect.Lists;
@@ -99,7 +103,7 @@
private View mHeader;
private LinearLayout mSwitches;
- private CheckBoxPreference mDataEnabled;
+ private SwitchPreference mDataEnabled;
private CheckBoxPreference mDisableAtLimit;
private View mDataEnabledView;
private View mDisableAtLimitView;
@@ -109,25 +113,28 @@
private Spinner mCycleSpinner;
private CycleAdapter mCycleAdapter;
- private boolean mSplit4G = false;
+ // TODO: persist show wifi flag
private boolean mShowWifi = false;
private int mTemplate = TEMPLATE_INVALID;
- private NetworkPolicy mPolicy;
+ private NetworkPolicyModifier mPolicyModifier;
private NetworkStatsHistory mHistory;
- // TODO: policy service should always provide valid stub policy
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setHasOptionsMenu(true);
mStatsService = INetworkStatsService.Stub.asInterface(
ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
mPolicyService = INetworkPolicyManager.Stub.asInterface(
ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
+
+ final Context context = getActivity();
+ final String subscriberId = getActiveSubscriberId(context);
+ mPolicyModifier = new NetworkPolicyModifier(mPolicyService, subscriberId);
+
+ setHasOptionsMenu(true);
}
@Override
@@ -147,7 +154,7 @@
mHeader = inflater.inflate(R.layout.data_usage_header, mListView, false);
mListView.addHeaderView(mHeader, null, false);
- mDataEnabled = new CheckBoxPreference(context);
+ mDataEnabled = new SwitchPreference(context);
mDisableAtLimit = new CheckBoxPreference(context);
// kick refresh once to force-create views
@@ -185,6 +192,9 @@
public void onResume() {
super.onResume();
+ // read current policy state from service
+ mPolicyModifier.read();
+
// this kicks off chain reaction which creates tabs, binds the body to
// selected network, and binds chart, cycles and detail list.
updateTabs();
@@ -196,13 +206,18 @@
}
@Override
- public boolean onOptionsItemSelected(MenuItem item) {
- // TODO: persist checked-ness of options to restore tabs later
+ public void onPrepareOptionsMenu(Menu menu) {
+ final MenuItem split4g = menu.findItem(R.id.action_split_4g);
+ split4g.setChecked(mPolicyModifier.isMobilePolicySplit());
+ }
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_split_4g: {
- mSplit4G = !item.isChecked();
- item.setChecked(mSplit4G);
+ final boolean mobileSplit = !item.isChecked();
+ mPolicyModifier.setMobilePolicySplit(mobileSplit);
+ item.setChecked(mPolicyModifier.isMobilePolicySplit());
updateTabs();
return true;
}
@@ -217,24 +232,23 @@
}
/**
- * Rebuild all tabs based on {@link #mSplit4G} and {@link #mShowWifi},
- * hiding the tabs entirely when applicable. Selects first tab, and kicks
- * off a full rebind of body contents.
+ * Rebuild all tabs based on {@link NetworkPolicyModifier} and
+ * {@link #mShowWifi}, hiding the tabs entirely when applicable. Selects
+ * first tab, and kicks off a full rebind of body contents.
*/
private void updateTabs() {
- // TODO: persist/restore if user wants mobile split, or wifi visibility
-
- final boolean tabsVisible = mSplit4G || mShowWifi;
+ final boolean mobileSplit = mPolicyModifier.isMobilePolicySplit();
+ final boolean tabsVisible = mobileSplit || mShowWifi;
mTabWidget.setVisibility(tabsVisible ? View.VISIBLE : View.GONE);
mTabHost.clearAllTabs();
- if (mSplit4G) {
+ if (mobileSplit) {
mTabHost.addTab(buildTabSpec(TAB_3G, R.string.data_usage_tab_3g));
mTabHost.addTab(buildTabSpec(TAB_4G, R.string.data_usage_tab_4g));
}
if (mShowWifi) {
- if (!mSplit4G) {
+ if (!mobileSplit) {
mTabHost.addTab(buildTabSpec(TAB_MOBILE, R.string.data_usage_tab_mobile));
}
mTabHost.addTab(buildTabSpec(TAB_WIFI, R.string.data_usage_tab_wifi));
@@ -323,8 +337,7 @@
mDataEnabled.setChecked(true);
try {
- // load policy and stats for current template
- mPolicy = mPolicyService.getNetworkPolicy(mTemplate, null);
+ // load stats for current template
mHistory = mStatsService.getHistoryForNetwork(mTemplate);
} catch (RemoteException e) {
// since we can't do much without policy or history, and we don't
@@ -332,20 +345,10 @@
throw new RuntimeException("problem reading network policy or stats", e);
}
- // TODO: eventually service will always provide stub policy
- if (mPolicy == null) {
- mPolicy = new NetworkPolicy(1, 4 * GB_IN_BYTES, -1);
- }
-
// bind chart to historical stats
- mChart.bindNetworkPolicy(mPolicy);
mChart.bindNetworkStats(mHistory);
- // generate cycle list based on policy and available history
- updateCycleList();
-
- // reflect policy limit in checkbox
- mDisableAtLimit.setChecked(mPolicy.limitBytes != -1);
+ updatePolicy();
// force scroll to top of body
mListView.smoothScrollToPosition(0);
@@ -355,6 +358,24 @@
}
/**
+ * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for
+ * current {@link #mTemplate}.
+ */
+ private void updatePolicy() {
+ final NetworkPolicy policy = mPolicyModifier.getPolicy(mTemplate);
+
+ // reflect policy limit in checkbox
+ mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED);
+ mChart.bindNetworkPolicy(policy);
+
+ // generate cycle list based on policy and available history
+ updateCycleList(policy);
+
+ // kick preference views so they rebind from changes above
+ refreshPreferenceViews();
+ }
+
+ /**
* Return full time bounds (earliest and latest time recorded) of the given
* {@link NetworkStatsHistory}.
*/
@@ -376,7 +397,7 @@
* and available {@link NetworkStatsHistory} data. Always selects the newest
* item, updating the inspection range on {@link #mChart}.
*/
- private void updateCycleList() {
+ private void updateCycleList(NetworkPolicy policy) {
mCycleAdapter.clear();
final Context context = mCycleSpinner.getContext();
@@ -385,28 +406,36 @@
final long historyStart = bounds[0];
final long historyEnd = bounds[1];
- // find the next cycle boundary
- long cycleEnd = computeNextCycleBoundary(historyEnd, mPolicy);
+ if (policy != null) {
+ // find the next cycle boundary
+ long cycleEnd = computeNextCycleBoundary(historyEnd, policy);
- int guardCount = 0;
+ int guardCount = 0;
- // walk backwards, generating all valid cycle ranges
- while (cycleEnd > historyStart) {
- final long cycleStart = computeLastCycleBoundary(cycleEnd, mPolicy);
- Log.d(TAG, "generating cs=" + cycleStart + " to ce=" + cycleEnd + " waiting for hs="
- + historyStart);
- mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd));
- cycleEnd = cycleStart;
+ // walk backwards, generating all valid cycle ranges
+ while (cycleEnd > historyStart) {
+ final long cycleStart = computeLastCycleBoundary(cycleEnd, policy);
+ Log.d(TAG, "generating cs=" + cycleStart + " to ce=" + cycleEnd + " waiting for hs="
+ + historyStart);
+ mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd));
+ cycleEnd = cycleStart;
- // TODO: remove this guard once we have better testing
- if (guardCount++ > 50) {
- Log.wtf(TAG, "stuck generating ranges for bounds=" + Arrays.toString(bounds)
- + " and policy=" + mPolicy);
+ // TODO: remove this guard once we have better testing
+ if (guardCount++ > 50) {
+ Log.wtf(TAG, "stuck generating ranges for bounds=" + Arrays.toString(bounds)
+ + " and policy=" + policy);
+ }
}
- }
- // one last cycle entry to change date
- mCycleAdapter.add(new CycleChangeItem(context));
+ // one last cycle entry to modify policy cycle day
+ mCycleAdapter.add(new CycleChangeItem(context));
+
+ } else {
+ // no valid cycle; show all data
+ // TODO: offer simple ranges like "last week" etc
+ mCycleAdapter.add(new CycleItem(context, historyStart, historyEnd));
+
+ }
// force pick the current cycle (first item)
mCycleSpinner.setSelection(0);
@@ -438,11 +467,11 @@
mDisableAtLimit.setChecked(disableAtLimit);
refreshPreferenceViews();
- // TODO: push updated policy to service
+ // TODO: create policy if none exists
// TODO: show interstitial warning dialog to user
- final long limitBytes = disableAtLimit ? 5 * GB_IN_BYTES : -1;
- mPolicy = new NetworkPolicy(mPolicy.cycleDay, mPolicy.warningBytes, limitBytes);
- mChart.bindNetworkPolicy(mPolicy);
+ final long limitBytes = disableAtLimit ? 5 * GB_IN_BYTES : LIMIT_DISABLED;
+ mPolicyModifier.setPolicyLimitBytes(mTemplate, limitBytes);
+ updatePolicy();
}
};
@@ -500,6 +529,12 @@
}
}
+ private static String getActiveSubscriberId(Context context) {
+ final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ return telephony.getSubscriberId();
+ }
+
private DataUsageChartListener mChartListener = new DataUsageChartListener() {
/** {@inheritDoc} */
public void onInspectRangeChanged() {
@@ -508,26 +543,20 @@
}
/** {@inheritDoc} */
- public void onLimitsChanged() {
- if (LOGD) Log.d(TAG, "onLimitsChanged()");
-
- // redefine policy and persist into service
- // TODO: kick this onto background thread, since service touches disk
-
- // TODO: remove this mPolicy null check, since later service will
- // always define baseline value.
- final int cycleDay = mPolicy != null ? mPolicy.cycleDay : 1;
+ public void onWarningChanged() {
+ if (LOGD) Log.d(TAG, "onWarningChanged()");
final long warningBytes = mChart.getWarningBytes();
- final long limitBytes = mDisableAtLimit.isChecked() ? -1 : mChart.getLimitBytes();
+ mPolicyModifier.setPolicyWarningBytes(mTemplate, warningBytes);
+ updatePolicy();
+ }
- mPolicy = new NetworkPolicy(cycleDay, warningBytes, limitBytes);
- if (LOGD) Log.d(TAG, "persisting policy=" + mPolicy);
-
- try {
- mPolicyService.setNetworkPolicy(mTemplate, null, mPolicy);
- } catch (RemoteException e) {
- Log.w(TAG, "problem persisting policy", e);
- }
+ /** {@inheritDoc} */
+ public void onLimitChanged() {
+ if (LOGD) Log.d(TAG, "onLimitChanged()");
+ final long limitBytes = mDisableAtLimit.isChecked() ? mChart.getLimitBytes()
+ : LIMIT_DISABLED;
+ mPolicyModifier.setPolicyLimitBytes(mTemplate, limitBytes);
+ updatePolicy();
}
};
@@ -605,7 +634,7 @@
public void bindStats(NetworkStats stats) {
mItems.clear();
- for (int i = 0; i < stats.length(); i++) {
+ for (int i = 0; i < stats.size; i++) {
final AppUsageItem item = new AppUsageItem();
item.uid = stats.uid[i];
item.total = stats.rx[i] + stats.tx[i];
diff --git a/src/com/android/settings/net/NetworkPolicyModifier.java b/src/com/android/settings/net/NetworkPolicyModifier.java
new file mode 100644
index 0000000..1d8aca3
--- /dev/null
+++ b/src/com/android/settings/net/NetworkPolicyModifier.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.net;
+
+import static android.net.TrafficStats.TEMPLATE_MOBILE_3G_LOWER;
+import static android.net.TrafficStats.TEMPLATE_MOBILE_4G;
+import static android.net.TrafficStats.TEMPLATE_MOBILE_ALL;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.net.INetworkPolicyManager;
+import android.net.NetworkPolicy;
+import android.os.AsyncTask;
+import android.os.RemoteException;
+
+import com.android.internal.util.Objects;
+import com.google.android.collect.Lists;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class to modify list of {@link NetworkPolicy}. Specifically knows
+ * about which policies can coexist.
+ */
+public class NetworkPolicyModifier {
+
+ private INetworkPolicyManager mPolicyService;
+ private String mSubscriberId;
+
+ private ArrayList<NetworkPolicy> mPolicies = Lists.newArrayList();
+
+ public NetworkPolicyModifier(INetworkPolicyManager policyService, String subscriberId) {
+ mPolicyService = checkNotNull(policyService);
+ mSubscriberId = subscriberId;
+ }
+
+ public void read() {
+ try {
+ final NetworkPolicy[] policies = mPolicyService.getNetworkPolicies();
+ mPolicies.clear();
+ for (NetworkPolicy policy : policies) {
+ mPolicies.add(policy);
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException("problem reading policies", e);
+ }
+ }
+
+ public void writeAsync() {
+ // TODO: consider making more robust by passing through service
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ write();
+ return null;
+ }
+ }.execute();
+ }
+
+ public void write() {
+ try {
+ final NetworkPolicy[] policies = mPolicies.toArray(new NetworkPolicy[mPolicies.size()]);
+ mPolicyService.setNetworkPolicies(policies);
+ } catch (RemoteException e) {
+ throw new RuntimeException("problem reading policies", e);
+ }
+ }
+
+ public NetworkPolicy getPolicy(int networkTemplate) {
+ for (NetworkPolicy policy : mPolicies) {
+ if (policy.networkTemplate == networkTemplate
+ && Objects.equal(policy.subscriberId, mSubscriberId)) {
+ return policy;
+ }
+ }
+ return null;
+ }
+
+ public void setPolicyCycleDay(int networkTemplate, int cycleDay) {
+ getPolicy(networkTemplate).cycleDay = cycleDay;
+ writeAsync();
+ }
+
+ public void setPolicyWarningBytes(int networkTemplate, long warningBytes) {
+ getPolicy(networkTemplate).warningBytes = warningBytes;
+ writeAsync();
+ }
+
+ public void setPolicyLimitBytes(int networkTemplate, long limitBytes) {
+ getPolicy(networkTemplate).limitBytes = limitBytes;
+ writeAsync();
+ }
+
+ public boolean isMobilePolicySplit() {
+ return getPolicy(TEMPLATE_MOBILE_3G_LOWER) != null && getPolicy(TEMPLATE_MOBILE_4G) != null;
+ }
+
+ public void setMobilePolicySplit(boolean split) {
+ final boolean beforeSplit = isMobilePolicySplit();
+ if (split == beforeSplit) {
+ // already in requested state; skip
+ return;
+
+ } else if (beforeSplit && !split) {
+ // combine, picking most restrictive policy
+ final NetworkPolicy policy3g = getPolicy(TEMPLATE_MOBILE_3G_LOWER);
+ final NetworkPolicy policy4g = getPolicy(TEMPLATE_MOBILE_4G);
+
+ final NetworkPolicy restrictive = policy3g.compareTo(policy4g) < 0 ? policy3g
+ : policy4g;
+ mPolicies.remove(policy3g);
+ mPolicies.remove(policy4g);
+ mPolicies.add(new NetworkPolicy(TEMPLATE_MOBILE_ALL, restrictive.subscriberId,
+ restrictive.cycleDay, restrictive.warningBytes, restrictive.limitBytes));
+ writeAsync();
+
+ } else if (!beforeSplit && split) {
+ // duplicate existing policy into two rules
+ final NetworkPolicy policyAll = getPolicy(TEMPLATE_MOBILE_ALL);
+ mPolicies.remove(policyAll);
+ mPolicies.add(
+ new NetworkPolicy(TEMPLATE_MOBILE_3G_LOWER, policyAll.subscriberId,
+ policyAll.cycleDay, policyAll.warningBytes, policyAll.limitBytes));
+ mPolicies.add(
+ new NetworkPolicy(TEMPLATE_MOBILE_4G, policyAll.subscriberId,
+ policyAll.cycleDay, policyAll.warningBytes, policyAll.limitBytes));
+ writeAsync();
+
+ }
+ }
+
+}
diff --git a/src/com/android/settings/widget/DataUsageChartView.java b/src/com/android/settings/widget/DataUsageChartView.java
index defa953..6a702d0 100644
--- a/src/com/android/settings/widget/DataUsageChartView.java
+++ b/src/com/android/settings/widget/DataUsageChartView.java
@@ -21,6 +21,7 @@
import android.net.NetworkPolicy;
import android.net.NetworkStatsHistory;
import android.text.format.DateUtils;
+import android.view.View;
import com.android.settings.widget.ChartSweepView.OnSweepListener;
@@ -44,7 +45,8 @@
public interface DataUsageChartListener {
public void onInspectRangeChanged();
- public void onLimitsChanged();
+ public void onWarningChanged();
+ public void onLimitChanged();
}
private DataUsageChartListener mListener;
@@ -78,6 +80,9 @@
mSeries.bindSweepRange(mSweepTime1, mSweepTime2);
+ mSweepDataWarn.addOnSweepListener(mWarningListener);
+ mSweepDataLimit.addOnSweepListener(mLimitListener);
+
mSweepTime1.addOnSweepListener(mSweepListener);
mSweepTime2.addOnSweepListener(mSweepListener);
@@ -92,15 +97,29 @@
}
public void bindNetworkPolicy(NetworkPolicy policy) {
- if (policy.limitBytes != -1) {
+ if (policy == null) {
+ mSweepDataLimit.setVisibility(View.INVISIBLE);
+ mSweepDataWarn.setVisibility(View.INVISIBLE);
+ return;
+ }
+
+ if (policy.limitBytes != NetworkPolicy.LIMIT_DISABLED) {
+ mSweepDataLimit.setVisibility(View.VISIBLE);
mSweepDataLimit.setValue(policy.limitBytes);
mSweepDataLimit.setEnabled(true);
} else {
+ // TODO: set limit default based on axis maximum
+ mSweepDataLimit.setVisibility(View.VISIBLE);
mSweepDataLimit.setValue(5 * GB_IN_BYTES);
mSweepDataLimit.setEnabled(false);
}
- mSweepDataWarn.setValue(policy.warningBytes);
+ if (policy.warningBytes != NetworkPolicy.WARNING_DISABLED) {
+ mSweepDataWarn.setVisibility(View.VISIBLE);
+ mSweepDataWarn.setValue(policy.warningBytes);
+ } else {
+ mSweepDataWarn.setVisibility(View.INVISIBLE);
+ }
}
private OnSweepListener mSweepListener = new OnSweepListener() {
@@ -115,6 +134,22 @@
}
};
+ private OnSweepListener mWarningListener = new OnSweepListener() {
+ public void onSweep(ChartSweepView sweep, boolean sweepDone) {
+ if (sweepDone && mListener != null) {
+ mListener.onWarningChanged();
+ }
+ }
+ };
+
+ private OnSweepListener mLimitListener = new OnSweepListener() {
+ public void onSweep(ChartSweepView sweep, boolean sweepDone) {
+ if (sweepDone && mListener != null) {
+ mListener.onLimitChanged();
+ }
+ }
+ };
+
/**
* Return current inspection range (start and end time) based on internal
* {@link ChartSweepView} positions.