blob: ef2282aef970e61910365c08f92b0092e9549a8e [file] [log] [blame]
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -07001/*
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;
18
Jeff Sharkey29d56b32011-06-20 17:06:52 -070019import static android.net.ConnectivityManager.TYPE_MOBILE;
20import static android.net.ConnectivityManager.TYPE_WIMAX;
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -070021import static android.net.NetworkPolicy.LIMIT_DISABLED;
Jeff Sharkeydd6efe12011-06-15 10:31:41 -070022import static android.net.NetworkPolicyManager.ACTION_DATA_USAGE_LIMIT;
Jeff Sharkeydd6efe12011-06-15 10:31:41 -070023import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
Jeff Sharkey8a503642011-06-10 13:31:21 -070024import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
25import static android.net.NetworkPolicyManager.computeNextCycleBoundary;
Jeff Sharkeya662e492011-06-18 21:57:06 -070026import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
27import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
28import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
29import static android.net.NetworkTemplate.MATCH_WIFI;
Jeff Sharkey8a503642011-06-10 13:31:21 -070030import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
Jeff Sharkey29d56b32011-06-20 17:06:52 -070031import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070032
Jeff Sharkey4c72ae52011-06-14 15:01:18 -070033import android.app.AlertDialog;
34import android.app.Dialog;
35import android.app.DialogFragment;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070036import android.app.Fragment;
37import android.content.Context;
Jeff Sharkey4c72ae52011-06-14 15:01:18 -070038import android.content.DialogInterface;
Jeff Sharkey4dfa6602011-06-13 00:42:03 -070039import android.content.Intent;
Jeff Sharkey29d56b32011-06-20 17:06:52 -070040import android.content.SharedPreferences;
Jeff Sharkey8e911d72011-06-14 22:41:21 -070041import android.content.pm.ApplicationInfo;
42import android.content.pm.PackageInfo;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070043import android.content.pm.PackageManager;
Jeff Sharkey8e911d72011-06-14 22:41:21 -070044import android.content.pm.PackageManager.NameNotFoundException;
Jeff Sharkey29d56b32011-06-20 17:06:52 -070045import android.net.ConnectivityManager;
Jeff Sharkey8a503642011-06-10 13:31:21 -070046import android.net.INetworkPolicyManager;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070047import android.net.INetworkStatsService;
Jeff Sharkey8a503642011-06-10 13:31:21 -070048import android.net.NetworkPolicy;
Jeff Sharkeydd6efe12011-06-15 10:31:41 -070049import android.net.NetworkPolicyManager;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070050import android.net.NetworkStats;
51import android.net.NetworkStatsHistory;
Jeff Sharkeya662e492011-06-18 21:57:06 -070052import android.net.NetworkTemplate;
Jeff Sharkeyaa5260e2011-06-14 23:21:59 -070053import android.os.AsyncTask;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070054import android.os.Bundle;
55import android.os.RemoteException;
56import android.os.ServiceManager;
Jeff Sharkey8a503642011-06-10 13:31:21 -070057import android.preference.Preference;
Jeff Sharkey4dfa6602011-06-13 00:42:03 -070058import android.preference.PreferenceActivity;
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -070059import android.telephony.TelephonyManager;
Jeff Sharkey8e911d72011-06-14 22:41:21 -070060import android.text.TextUtils;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070061import android.text.format.DateUtils;
62import android.text.format.Formatter;
Jeff Sharkey8a503642011-06-10 13:31:21 -070063import android.text.format.Time;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070064import android.util.Log;
65import android.view.LayoutInflater;
Jeff Sharkey8a503642011-06-10 13:31:21 -070066import android.view.Menu;
67import android.view.MenuInflater;
68import android.view.MenuItem;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070069import android.view.View;
70import android.view.ViewGroup;
Jeff Sharkey8a503642011-06-10 13:31:21 -070071import android.widget.AbsListView;
72import android.widget.AdapterView;
73import android.widget.AdapterView.OnItemClickListener;
74import android.widget.AdapterView.OnItemSelectedListener;
75import android.widget.ArrayAdapter;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070076import android.widget.BaseAdapter;
Jeff Sharkey29d56b32011-06-20 17:06:52 -070077import android.widget.CheckBox;
78import android.widget.CompoundButton;
79import android.widget.CompoundButton.OnCheckedChangeListener;
Jeff Sharkey8a503642011-06-10 13:31:21 -070080import android.widget.LinearLayout;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070081import android.widget.ListView;
Jeff Sharkey4c72ae52011-06-14 15:01:18 -070082import android.widget.NumberPicker;
Jeff Sharkey8a503642011-06-10 13:31:21 -070083import android.widget.Spinner;
Jeff Sharkey29d56b32011-06-20 17:06:52 -070084import android.widget.Switch;
Jeff Sharkey8a503642011-06-10 13:31:21 -070085import android.widget.TabHost;
86import android.widget.TabHost.OnTabChangeListener;
87import android.widget.TabHost.TabContentFactory;
88import android.widget.TabHost.TabSpec;
89import android.widget.TabWidget;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070090import android.widget.TextView;
91
Jeff Sharkey29d56b32011-06-20 17:06:52 -070092import com.android.internal.telephony.Phone;
Jeff Sharkeya662e492011-06-18 21:57:06 -070093import com.android.settings.net.NetworkPolicyEditor;
Jeff Sharkey8a503642011-06-10 13:31:21 -070094import com.android.settings.widget.DataUsageChartView;
95import com.android.settings.widget.DataUsageChartView.DataUsageChartListener;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070096import com.google.android.collect.Lists;
97
98import java.util.ArrayList;
Jeff Sharkey8a503642011-06-10 13:31:21 -070099import java.util.Arrays;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700100import java.util.Collections;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700101import java.util.Locale;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700102
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700103/**
104 * Panel show data usage history across various networks, including options to
105 * inspect based on usage cycle and control through {@link NetworkPolicy}.
106 */
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700107public class DataUsageSummary extends Fragment {
108 private static final String TAG = "DataUsage";
Jeff Sharkey8a503642011-06-10 13:31:21 -0700109 private static final boolean LOGD = true;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700110
Jeff Sharkey8a503642011-06-10 13:31:21 -0700111 private static final int TEMPLATE_INVALID = -1;
112
113 private static final String TAB_3G = "3g";
114 private static final String TAB_4G = "4g";
115 private static final String TAB_MOBILE = "mobile";
116 private static final String TAB_WIFI = "wifi";
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700117
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700118 private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
119 private static final String TAG_CYCLE_EDITOR = "cycleEditor";
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700120 private static final String TAG_POLICY_LIMIT = "policyLimit";
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700121
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700122 private static final long KB_IN_BYTES = 1024;
123 private static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
124 private static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
125
126 private INetworkStatsService mStatsService;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700127 private INetworkPolicyManager mPolicyService;
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700128 private ConnectivityManager mConnService;
129
130 private static final String PREF_FILE = "data_usage";
131 private static final String PREF_SHOW_WIFI = "show_wifi";
132
133 private SharedPreferences mPrefs;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700134
Jeff Sharkey8a503642011-06-10 13:31:21 -0700135 private TabHost mTabHost;
136 private TabWidget mTabWidget;
137 private ListView mListView;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700138 private DataUsageAdapter mAdapter;
139
Jeff Sharkey8a503642011-06-10 13:31:21 -0700140 private View mHeader;
141 private LinearLayout mSwitches;
142
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700143 private Switch mDataEnabled;
144 private CheckBox mDisableAtLimit;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700145 private View mDataEnabledView;
146 private View mDisableAtLimitView;
147
148 private DataUsageChartView mChart;
149
150 private Spinner mCycleSpinner;
151 private CycleAdapter mCycleAdapter;
152
Jeff Sharkey8a503642011-06-10 13:31:21 -0700153 private boolean mShowWifi = false;
154
Jeff Sharkeya662e492011-06-18 21:57:06 -0700155 private NetworkTemplate mTemplate = null;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700156
Jeff Sharkeya662e492011-06-18 21:57:06 -0700157 private NetworkPolicyEditor mPolicyEditor;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700158 private NetworkStatsHistory mHistory;
159
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700160 private String mIntentTab = null;
161
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700162 /** Flag used to ignore listeners during binding. */
163 private boolean mBinding;
164
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700165 @Override
Jeff Sharkey8a503642011-06-10 13:31:21 -0700166 public void onCreate(Bundle savedInstanceState) {
167 super.onCreate(savedInstanceState);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700168
169 mStatsService = INetworkStatsService.Stub.asInterface(
170 ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
Jeff Sharkey8a503642011-06-10 13:31:21 -0700171 mPolicyService = INetworkPolicyManager.Stub.asInterface(
172 ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700173 mConnService = (ConnectivityManager) getActivity().getSystemService(
174 Context.CONNECTIVITY_SERVICE);
175 mPrefs = getActivity().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700176
Jeff Sharkeya662e492011-06-18 21:57:06 -0700177 mPolicyEditor = new NetworkPolicyEditor(mPolicyService);
178 mPolicyEditor.read();
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700179
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700180 mShowWifi = mPrefs.getBoolean(PREF_SHOW_WIFI, false);
181
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700182 setHasOptionsMenu(true);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700183 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700184
Jeff Sharkey8a503642011-06-10 13:31:21 -0700185 @Override
186 public View onCreateView(LayoutInflater inflater, ViewGroup container,
187 Bundle savedInstanceState) {
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700188
Jeff Sharkey8a503642011-06-10 13:31:21 -0700189 final Context context = inflater.getContext();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700190 final View view = inflater.inflate(R.layout.data_usage_summary, container, false);
191
Jeff Sharkey8a503642011-06-10 13:31:21 -0700192 mTabHost = (TabHost) view.findViewById(android.R.id.tabhost);
193 mTabWidget = (TabWidget) view.findViewById(android.R.id.tabs);
194 mListView = (ListView) view.findViewById(android.R.id.list);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700195
Jeff Sharkey8a503642011-06-10 13:31:21 -0700196 mTabHost.setup();
197 mTabHost.setOnTabChangedListener(mTabListener);
198
199 mHeader = inflater.inflate(R.layout.data_usage_header, mListView, false);
200 mListView.addHeaderView(mHeader, null, false);
201
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700202 mDataEnabled = new Switch(inflater.getContext());
203 mDataEnabledView = inflatePreference(inflater, mSwitches, mDataEnabled);
204 mDataEnabled.setOnCheckedChangeListener(mDataEnabledListener);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700205
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700206 mDisableAtLimit = new CheckBox(inflater.getContext());
207 mDisableAtLimitView = inflatePreference(inflater, mSwitches, mDisableAtLimit);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700208 mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener);
209
210 mSwitches = (LinearLayout) mHeader.findViewById(R.id.switches);
211 mSwitches.addView(mDataEnabledView);
212 mSwitches.addView(mDisableAtLimitView);
213
214 mCycleSpinner = (Spinner) mHeader.findViewById(R.id.cycles);
215 mCycleAdapter = new CycleAdapter(context);
216 mCycleSpinner.setAdapter(mCycleAdapter);
217 mCycleSpinner.setOnItemSelectedListener(mCycleListener);
218
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700219 final int chartHeight = getResources().getDimensionPixelSize(
220 R.dimen.data_usage_chart_height);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700221 mChart = new DataUsageChartView(context);
222 mChart.setListener(mChartListener);
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700223 mChart.setLayoutParams(new AbsListView.LayoutParams(MATCH_PARENT, chartHeight));
Jeff Sharkey8a503642011-06-10 13:31:21 -0700224 mListView.addHeaderView(mChart, null, false);
225
226 mAdapter = new DataUsageAdapter();
227 mListView.setOnItemClickListener(mListListener);
228 mListView.setAdapter(mAdapter);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700229
230 return view;
231 }
232
233 @Override
234 public void onResume() {
235 super.onResume();
236
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700237 // pick default tab based on incoming intent
238 final Intent intent = getActivity().getIntent();
239 mIntentTab = computeTabFromIntent(intent);
240
Jeff Sharkey8a503642011-06-10 13:31:21 -0700241 // this kicks off chain reaction which creates tabs, binds the body to
242 // selected network, and binds chart, cycles and detail list.
243 updateTabs();
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700244
245 // template and tab has been selected; show dialog if limit passed
246 final String action = intent.getAction();
247 if (ACTION_DATA_USAGE_LIMIT.equals(action)) {
248 PolicyLimitFragment.show(this);
249 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700250 }
251
Jeff Sharkey8a503642011-06-10 13:31:21 -0700252 @Override
253 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
254 inflater.inflate(R.menu.data_usage, menu);
255 }
256
257 @Override
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700258 public void onPrepareOptionsMenu(Menu menu) {
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700259 final Context context = getActivity();
260
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700261 final MenuItem split4g = menu.findItem(R.id.action_split_4g);
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700262 split4g.setVisible(hasMobile4gRadio(context));
Jeff Sharkeya662e492011-06-18 21:57:06 -0700263 split4g.setChecked(isMobilePolicySplit());
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700264
265 final MenuItem showWifi = menu.findItem(R.id.action_show_wifi);
266 showWifi.setVisible(hasMobileRadio(context) && hasWifiRadio(context));
267 showWifi.setChecked(mShowWifi);
268
269 final MenuItem settings = menu.findItem(R.id.action_settings);
270 settings.setVisible(split4g.isVisible() || showWifi.isVisible());
271
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700272 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700273
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700274 @Override
275 public boolean onOptionsItemSelected(MenuItem item) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700276 switch (item.getItemId()) {
277 case R.id.action_split_4g: {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700278 final boolean mobileSplit = !item.isChecked();
Jeff Sharkeya662e492011-06-18 21:57:06 -0700279 setMobilePolicySplit(mobileSplit);
280 item.setChecked(isMobilePolicySplit());
Jeff Sharkey8a503642011-06-10 13:31:21 -0700281 updateTabs();
282 return true;
283 }
284 case R.id.action_show_wifi: {
285 mShowWifi = !item.isChecked();
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700286 mPrefs.edit().putBoolean(PREF_SHOW_WIFI, mShowWifi).apply();
Jeff Sharkey8a503642011-06-10 13:31:21 -0700287 item.setChecked(mShowWifi);
288 updateTabs();
289 return true;
290 }
291 }
292 return false;
293 }
294
Jeff Sharkey94a90952011-06-13 22:31:09 -0700295 @Override
296 public void onDestroyView() {
297 super.onDestroyView();
298
299 mDataEnabledView = null;
300 mDisableAtLimitView = null;
301 }
302
Jeff Sharkey8a503642011-06-10 13:31:21 -0700303 /**
Jeff Sharkeya662e492011-06-18 21:57:06 -0700304 * Rebuild all tabs based on {@link NetworkPolicyEditor} and
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700305 * {@link #mShowWifi}, hiding the tabs entirely when applicable. Selects
306 * first tab, and kicks off a full rebind of body contents.
Jeff Sharkey8a503642011-06-10 13:31:21 -0700307 */
308 private void updateTabs() {
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700309 final Context context = getActivity();
Jeff Sharkey8a503642011-06-10 13:31:21 -0700310 mTabHost.clearAllTabs();
311
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700312 final boolean mobileSplit = isMobilePolicySplit();
313 if (mobileSplit && hasMobile4gRadio(context)) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700314 mTabHost.addTab(buildTabSpec(TAB_3G, R.string.data_usage_tab_3g));
315 mTabHost.addTab(buildTabSpec(TAB_4G, R.string.data_usage_tab_4g));
316 }
317
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700318 if (mShowWifi && hasWifiRadio(context) && hasMobileRadio(context)) {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700319 if (!mobileSplit) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700320 mTabHost.addTab(buildTabSpec(TAB_MOBILE, R.string.data_usage_tab_mobile));
321 }
322 mTabHost.addTab(buildTabSpec(TAB_WIFI, R.string.data_usage_tab_wifi));
323 }
324
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700325 final boolean hasTabs = mTabWidget.getTabCount() > 0;
326 mTabWidget.setVisibility(hasTabs ? View.VISIBLE : View.GONE);
327 if (hasTabs) {
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700328 if (mIntentTab != null) {
329 // select default tab, which will kick off updateBody()
330 mTabHost.setCurrentTabByTag(mIntentTab);
331 } else {
332 // select first tab, which will kick off updateBody()
333 mTabHost.setCurrentTab(0);
334 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700335 } else {
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700336 // no tabs visible; update body manually
Jeff Sharkey8a503642011-06-10 13:31:21 -0700337 updateBody();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700338 }
339 }
340
Jeff Sharkey8a503642011-06-10 13:31:21 -0700341 /**
342 * Factory that provide empty {@link View} to make {@link TabHost} happy.
343 */
344 private TabContentFactory mEmptyTabContent = new TabContentFactory() {
345 /** {@inheritDoc} */
346 public View createTabContent(String tag) {
347 return new View(mTabHost.getContext());
348 }
349 };
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700350
Jeff Sharkey8a503642011-06-10 13:31:21 -0700351 /**
352 * Build {@link TabSpec} with thin indicator, and empty content.
353 */
354 private TabSpec buildTabSpec(String tag, int titleRes) {
355 final LayoutInflater inflater = LayoutInflater.from(mTabWidget.getContext());
356 final View indicator = inflater.inflate(
357 R.layout.tab_indicator_thin_holo, mTabWidget, false);
358 final TextView title = (TextView) indicator.findViewById(android.R.id.title);
359 title.setText(titleRes);
360 return mTabHost.newTabSpec(tag).setIndicator(indicator).setContent(mEmptyTabContent);
361 }
362
363 private OnTabChangeListener mTabListener = new OnTabChangeListener() {
364 /** {@inheritDoc} */
365 public void onTabChanged(String tabId) {
366 // user changed tab; update body
367 updateBody();
368 }
369 };
370
371 /**
372 * Update body content based on current tab. Loads
373 * {@link NetworkStatsHistory} and {@link NetworkPolicy} from system, and
374 * binds them to visible controls.
375 */
376 private void updateBody() {
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700377 mBinding = true;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700378
Jeff Sharkeya662e492011-06-18 21:57:06 -0700379 final Context context = getActivity();
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700380 final String tabTag = mTabHost.getCurrentTabTag();
381
382 final String currentTab;
383 if (tabTag != null) {
384 currentTab = tabTag;
385 } else if (hasMobileRadio(context)) {
386 currentTab = TAB_MOBILE;
387 } else if (hasWifiRadio(context)) {
388 currentTab = TAB_WIFI;
389 } else {
390 throw new IllegalStateException("no mobile or wifi radios");
391 }
Jeff Sharkeya662e492011-06-18 21:57:06 -0700392
Jeff Sharkey8a503642011-06-10 13:31:21 -0700393 if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab);
394
395 if (TAB_WIFI.equals(currentTab)) {
396 // wifi doesn't have any controls
397 mDataEnabledView.setVisibility(View.GONE);
398 mDisableAtLimitView.setVisibility(View.GONE);
Jeff Sharkeya662e492011-06-18 21:57:06 -0700399 mTemplate = new NetworkTemplate(MATCH_WIFI, null);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700400
401 } else {
402 // make sure we show for non-wifi
403 mDataEnabledView.setVisibility(View.VISIBLE);
404 mDisableAtLimitView.setVisibility(View.VISIBLE);
405 }
406
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700407 final String subscriberId = getActiveSubscriberId(context);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700408 if (TAB_MOBILE.equals(currentTab)) {
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700409 setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_mobile);
410 setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_mobile_limit);
411 mDataEnabled.setChecked(mConnService.getMobileDataEnabled());
Jeff Sharkeya662e492011-06-18 21:57:06 -0700412 mTemplate = new NetworkTemplate(MATCH_MOBILE_ALL, subscriberId);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700413
414 } else if (TAB_3G.equals(currentTab)) {
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700415 setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_3g);
416 setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_3g_limit);
417 // TODO: bind mDataEnabled to 3G radio state
Jeff Sharkeya662e492011-06-18 21:57:06 -0700418 mTemplate = new NetworkTemplate(MATCH_MOBILE_3G_LOWER, subscriberId);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700419
420 } else if (TAB_4G.equals(currentTab)) {
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700421 setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_4g);
422 setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_4g_limit);
423 // TODO: bind mDataEnabled to 4G radio state
Jeff Sharkeya662e492011-06-18 21:57:06 -0700424 mTemplate = new NetworkTemplate(MATCH_MOBILE_4G, subscriberId);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700425 }
426
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700427 try {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700428 // load stats for current template
Jeff Sharkey8a503642011-06-10 13:31:21 -0700429 mHistory = mStatsService.getHistoryForNetwork(mTemplate);
430 } catch (RemoteException e) {
431 // since we can't do much without policy or history, and we don't
432 // want to leave with half-baked UI, we bail hard.
433 throw new RuntimeException("problem reading network policy or stats", e);
434 }
435
Jeff Sharkey8a503642011-06-10 13:31:21 -0700436 // bind chart to historical stats
Jeff Sharkey8a503642011-06-10 13:31:21 -0700437 mChart.bindNetworkStats(mHistory);
438
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700439 updatePolicy(true);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700440
441 // force scroll to top of body
442 mListView.smoothScrollToPosition(0);
443
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700444 mBinding = false;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700445 }
446
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700447 private void setPolicyCycleDay(int cycleDay) {
448 if (LOGD) Log.d(TAG, "setPolicyCycleDay()");
Jeff Sharkeya662e492011-06-18 21:57:06 -0700449 mPolicyEditor.setPolicyCycleDay(mTemplate, cycleDay);
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700450 updatePolicy(true);
451 }
452
453 private void setPolicyWarningBytes(long warningBytes) {
454 if (LOGD) Log.d(TAG, "setPolicyWarningBytes()");
Jeff Sharkeya662e492011-06-18 21:57:06 -0700455 mPolicyEditor.setPolicyWarningBytes(mTemplate, warningBytes);
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700456 updatePolicy(false);
457 }
458
459 private void setPolicyLimitBytes(long limitBytes) {
460 if (LOGD) Log.d(TAG, "setPolicyLimitBytes()");
Jeff Sharkeya662e492011-06-18 21:57:06 -0700461 mPolicyEditor.setPolicyLimitBytes(mTemplate, limitBytes);
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700462 updatePolicy(false);
463 }
464
Jeff Sharkey8a503642011-06-10 13:31:21 -0700465 /**
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700466 * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for
467 * current {@link #mTemplate}.
468 */
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700469 private void updatePolicy(boolean refreshCycle) {
Jeff Sharkeya662e492011-06-18 21:57:06 -0700470 final NetworkPolicy policy = mPolicyEditor.getPolicy(mTemplate);
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700471
472 // reflect policy limit in checkbox
473 mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED);
474 mChart.bindNetworkPolicy(policy);
475
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700476 if (refreshCycle) {
477 // generate cycle list based on policy and available history
478 updateCycleList(policy);
479 }
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700480 }
481
482 /**
Jeff Sharkey8a503642011-06-10 13:31:21 -0700483 * Return full time bounds (earliest and latest time recorded) of the given
484 * {@link NetworkStatsHistory}.
485 */
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700486 public static long[] getHistoryBounds(NetworkStatsHistory history) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700487 final long currentTime = System.currentTimeMillis();
488
489 long start = currentTime;
490 long end = currentTime;
491 if (history.bucketCount > 0) {
492 start = history.bucketStart[0];
493 end = history.bucketStart[history.bucketCount - 1];
494 }
495
496 return new long[] { start, end };
497 }
498
499 /**
500 * Rebuild {@link #mCycleAdapter} based on {@link NetworkPolicy#cycleDay}
501 * and available {@link NetworkStatsHistory} data. Always selects the newest
502 * item, updating the inspection range on {@link #mChart}.
503 */
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700504 private void updateCycleList(NetworkPolicy policy) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700505 mCycleAdapter.clear();
506
507 final Context context = mCycleSpinner.getContext();
508
509 final long[] bounds = getHistoryBounds(mHistory);
510 final long historyStart = bounds[0];
511 final long historyEnd = bounds[1];
512
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700513 if (policy != null) {
514 // find the next cycle boundary
515 long cycleEnd = computeNextCycleBoundary(historyEnd, policy);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700516
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700517 int guardCount = 0;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700518
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700519 // walk backwards, generating all valid cycle ranges
520 while (cycleEnd > historyStart) {
521 final long cycleStart = computeLastCycleBoundary(cycleEnd, policy);
522 Log.d(TAG, "generating cs=" + cycleStart + " to ce=" + cycleEnd + " waiting for hs="
523 + historyStart);
524 mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd));
525 cycleEnd = cycleStart;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700526
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700527 // TODO: remove this guard once we have better testing
528 if (guardCount++ > 50) {
529 Log.wtf(TAG, "stuck generating ranges for bounds=" + Arrays.toString(bounds)
530 + " and policy=" + policy);
531 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700532 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700533
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700534 // one last cycle entry to modify policy cycle day
535 mCycleAdapter.add(new CycleChangeItem(context));
536
537 } else {
538 // no valid cycle; show all data
539 // TODO: offer simple ranges like "last week" etc
540 mCycleAdapter.add(new CycleItem(context, historyStart, historyEnd));
541
542 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700543
544 // force pick the current cycle (first item)
545 mCycleSpinner.setSelection(0);
546 mCycleListener.onItemSelected(mCycleSpinner, null, 0, 0);
547 }
548
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700549 private OnCheckedChangeListener mDataEnabledListener = new OnCheckedChangeListener() {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700550 /** {@inheritDoc} */
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700551 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
552 if (mBinding) return;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700553
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700554 final boolean dataEnabled = isChecked;
555 mDataEnabled.setChecked(dataEnabled);
556
557 switch (mTemplate.getMatchRule()) {
558 case MATCH_MOBILE_ALL: {
559 mConnService.setMobileDataEnabled(dataEnabled);
560 }
561 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700562 }
563 };
564
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700565 private View.OnClickListener mDisableAtLimitListener = new View.OnClickListener() {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700566 /** {@inheritDoc} */
567 public void onClick(View v) {
568 final boolean disableAtLimit = !mDisableAtLimit.isChecked();
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700569 if (disableAtLimit) {
570 // enabling limit; show confirmation dialog which eventually
571 // calls setPolicyLimitBytes() once user confirms.
572 ConfirmLimitFragment.show(DataUsageSummary.this);
573 } else {
574 setPolicyLimitBytes(LIMIT_DISABLED);
575 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700576 }
577 };
578
579 private OnItemClickListener mListListener = new OnItemClickListener() {
580 /** {@inheritDoc} */
581 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700582 final AppUsageItem app = (AppUsageItem) parent.getItemAtPosition(position);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700583
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700584 final Bundle args = new Bundle();
Jeff Sharkeya662e492011-06-18 21:57:06 -0700585 args.putParcelable(DataUsageAppDetail.EXTRA_NETWORK_TEMPLATE, mTemplate);
586 args.putInt(DataUsageAppDetail.EXTRA_UID, app.uid);
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700587
588 final PreferenceActivity activity = (PreferenceActivity) getActivity();
589 activity.startPreferencePanel(DataUsageAppDetail.class.getName(), args,
590 R.string.data_usage_summary_title, null, null, 0);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700591 }
592 };
593
594 private OnItemSelectedListener mCycleListener = new OnItemSelectedListener() {
595 /** {@inheritDoc} */
596 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
597 final CycleItem cycle = (CycleItem) parent.getItemAtPosition(position);
598 if (cycle instanceof CycleChangeItem) {
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700599 // show cycle editor; will eventually call setPolicyCycleDay()
600 // when user finishes editing.
601 CycleEditorFragment.show(DataUsageSummary.this);
602
603 // reset spinner to something other than "change cycle..."
604 mCycleSpinner.setSelection(0);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700605
606 } else {
Jeff Sharkey8e911d72011-06-14 22:41:21 -0700607 if (LOGD) {
608 Log.d(TAG, "showing cycle " + cycle + ", start=" + cycle.start + ", end="
609 + cycle.end + "]");
610 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700611
612 // update chart to show selected cycle, and update detail data
613 // to match updated sweep bounds.
614 final long[] bounds = getHistoryBounds(mHistory);
615 mChart.setVisibleRange(cycle.start, cycle.end, bounds[1]);
616
617 updateDetailData();
618 }
619 }
620
621 /** {@inheritDoc} */
622 public void onNothingSelected(AdapterView<?> parent) {
623 // ignored
624 }
625 };
626
627 /**
628 * Update {@link #mAdapter} with sorted list of applications data usage,
629 * based on current inspection from {@link #mChart}.
630 */
631 private void updateDetailData() {
632 if (LOGD) Log.d(TAG, "updateDetailData()");
633
Jeff Sharkeyaa5260e2011-06-14 23:21:59 -0700634 new AsyncTask<Void, Void, NetworkStats>() {
635 @Override
636 protected NetworkStats doInBackground(Void... params) {
637 try {
638 final long[] range = mChart.getInspectRange();
Jeff Sharkey827fde32011-06-19 20:56:31 -0700639 return mStatsService.getSummaryForAllUid(mTemplate, range[0], range[1], false);
Jeff Sharkeyaa5260e2011-06-14 23:21:59 -0700640 } catch (RemoteException e) {
641 Log.w(TAG, "problem reading stats");
642 }
643 return null;
644 }
645
646 @Override
647 protected void onPostExecute(NetworkStats stats) {
648 if (stats != null) {
649 mAdapter.bindStats(stats);
650 }
651 }
652 }.execute();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700653 }
654
Jeff Sharkeya662e492011-06-18 21:57:06 -0700655 private boolean isMobilePolicySplit() {
656 final String subscriberId = getActiveSubscriberId(getActivity());
657 return mPolicyEditor.isMobilePolicySplit(subscriberId);
658 }
659
660 private void setMobilePolicySplit(boolean split) {
661 final String subscriberId = getActiveSubscriberId(getActivity());
662 mPolicyEditor.setMobilePolicySplit(subscriberId, split);
663 }
664
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700665 private static String getActiveSubscriberId(Context context) {
666 final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
667 Context.TELEPHONY_SERVICE);
668 return telephony.getSubscriberId();
669 }
670
Jeff Sharkey8a503642011-06-10 13:31:21 -0700671 private DataUsageChartListener mChartListener = new DataUsageChartListener() {
672 /** {@inheritDoc} */
673 public void onInspectRangeChanged() {
674 if (LOGD) Log.d(TAG, "onInspectRangeChanged()");
675 updateDetailData();
676 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700677
Jeff Sharkey8a503642011-06-10 13:31:21 -0700678 /** {@inheritDoc} */
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700679 public void onWarningChanged() {
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700680 setPolicyWarningBytes(mChart.getWarningBytes());
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700681 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700682
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700683 /** {@inheritDoc} */
684 public void onLimitChanged() {
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700685 setPolicyLimitBytes(mChart.getLimitBytes());
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700686 }
687 };
688
689
690 /**
Jeff Sharkey8a503642011-06-10 13:31:21 -0700691 * List item that reflects a specific data usage cycle.
692 */
693 public static class CycleItem {
694 public CharSequence label;
695 public long start;
696 public long end;
697
698 private static final StringBuilder sBuilder = new StringBuilder(50);
699 private static final java.util.Formatter sFormatter = new java.util.Formatter(
700 sBuilder, Locale.getDefault());
701
702 CycleItem(CharSequence label) {
703 this.label = label;
704 }
705
706 public CycleItem(Context context, long start, long end) {
707 this.label = formatDateRangeUtc(context, start, end);
708 this.start = start;
709 this.end = end;
710 }
711
712 private static String formatDateRangeUtc(Context context, long start, long end) {
713 synchronized (sBuilder) {
714 sBuilder.setLength(0);
715 return DateUtils.formatDateRange(context, sFormatter, start, end,
716 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH,
717 Time.TIMEZONE_UTC).toString();
718 }
719 }
720
721 @Override
722 public String toString() {
723 return label.toString();
724 }
725 }
726
727 /**
728 * Special-case data usage cycle that triggers dialog to change
729 * {@link NetworkPolicy#cycleDay}.
730 */
731 public static class CycleChangeItem extends CycleItem {
732 public CycleChangeItem(Context context) {
733 super(context.getString(R.string.data_usage_change_cycle));
734 }
735 }
736
737 public static class CycleAdapter extends ArrayAdapter<CycleItem> {
738 public CycleAdapter(Context context) {
739 super(context, android.R.layout.simple_spinner_item);
740 setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
741 }
742 }
743
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700744 private static class AppUsageItem implements Comparable<AppUsageItem> {
745 public int uid;
746 public long total;
747
748 /** {@inheritDoc} */
749 public int compareTo(AppUsageItem another) {
750 return Long.compare(another.total, total);
751 }
752 }
753
Jeff Sharkey8a503642011-06-10 13:31:21 -0700754 /**
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700755 * Adapter of applications, sorted by total usage descending.
756 */
757 public static class DataUsageAdapter extends BaseAdapter {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700758 private ArrayList<AppUsageItem> mItems = Lists.newArrayList();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700759
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700760 public void bindStats(NetworkStats stats) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700761 mItems.clear();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700762
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700763 for (int i = 0; i < stats.size; i++) {
Jeff Sharkey8e911d72011-06-14 22:41:21 -0700764 final long total = stats.rx[i] + stats.tx[i];
Jeff Sharkey29d56b32011-06-20 17:06:52 -0700765 final AppUsageItem item = new AppUsageItem();
766 item.uid = stats.uid[i];
767 item.total = total;
768 mItems.add(item);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700769 }
770
Jeff Sharkey8a503642011-06-10 13:31:21 -0700771 Collections.sort(mItems);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700772 notifyDataSetChanged();
773 }
774
775 @Override
776 public int getCount() {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700777 return mItems.size();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700778 }
779
780 @Override
781 public Object getItem(int position) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700782 return mItems.get(position);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700783 }
784
785 @Override
786 public long getItemId(int position) {
787 return position;
788 }
789
790 @Override
791 public View getView(int position, View convertView, ViewGroup parent) {
792 if (convertView == null) {
793 convertView = LayoutInflater.from(parent.getContext()).inflate(
794 android.R.layout.simple_list_item_2, parent, false);
795 }
796
797 final Context context = parent.getContext();
798 final PackageManager pm = context.getPackageManager();
799
800 final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
801 final TextView text2 = (TextView) convertView.findViewById(android.R.id.text2);
802
Jeff Sharkey8a503642011-06-10 13:31:21 -0700803 final AppUsageItem item = mItems.get(position);
Jeff Sharkey8e911d72011-06-14 22:41:21 -0700804 text1.setText(resolveLabelForUid(pm, item.uid));
Jeff Sharkey8a503642011-06-10 13:31:21 -0700805 text2.setText(Formatter.formatFileSize(context, item.total));
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700806
807 return convertView;
808 }
809
810 }
811
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700812 /**
813 * Dialog to request user confirmation before setting
814 * {@link NetworkPolicy#limitBytes}.
815 */
816 public static class ConfirmLimitFragment extends DialogFragment {
817 public static final String EXTRA_MESSAGE_ID = "messageId";
818 public static final String EXTRA_LIMIT_BYTES = "limitBytes";
819
820 public static void show(DataUsageSummary parent) {
821 final Bundle args = new Bundle();
822
823 // TODO: customize default limits based on network template
Jeff Sharkeya662e492011-06-18 21:57:06 -0700824 switch (parent.mTemplate.getMatchRule()) {
825 case MATCH_MOBILE_3G_LOWER: {
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700826 args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_3g);
827 args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES);
828 break;
829 }
Jeff Sharkeya662e492011-06-18 21:57:06 -0700830 case MATCH_MOBILE_4G: {
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700831 args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_4g);
832 args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES);
833 break;
834 }
Jeff Sharkeya662e492011-06-18 21:57:06 -0700835 case MATCH_MOBILE_ALL: {
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700836 args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_mobile);
837 args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES);
838 break;
839 }
840 }
841
842 final ConfirmLimitFragment dialog = new ConfirmLimitFragment();
843 dialog.setArguments(args);
844 dialog.setTargetFragment(parent, 0);
845 dialog.show(parent.getFragmentManager(), TAG_CONFIRM_LIMIT);
846 }
847
848 @Override
849 public Dialog onCreateDialog(Bundle savedInstanceState) {
850 final Context context = getActivity();
851
852 final int messageId = getArguments().getInt(EXTRA_MESSAGE_ID);
853 final long limitBytes = getArguments().getLong(EXTRA_LIMIT_BYTES);
854
855 final AlertDialog.Builder builder = new AlertDialog.Builder(context);
856 builder.setTitle(R.string.data_usage_limit_dialog_title);
857 builder.setMessage(messageId);
858
859 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
860 public void onClick(DialogInterface dialog, int which) {
861 final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
862 if (target != null) {
863 target.setPolicyLimitBytes(limitBytes);
864 }
865 }
866 });
867
868 return builder.create();
869 }
870 }
871
872 /**
873 * Dialog to edit {@link NetworkPolicy#cycleDay}.
874 */
875 public static class CycleEditorFragment extends DialogFragment {
876 public static final String EXTRA_CYCLE_DAY = "cycleDay";
877
878 public static void show(DataUsageSummary parent) {
Jeff Sharkeya662e492011-06-18 21:57:06 -0700879 final NetworkPolicy policy = parent.mPolicyEditor.getPolicy(parent.mTemplate);
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700880 final Bundle args = new Bundle();
881 args.putInt(CycleEditorFragment.EXTRA_CYCLE_DAY, policy.cycleDay);
882
883 final CycleEditorFragment dialog = new CycleEditorFragment();
884 dialog.setArguments(args);
885 dialog.setTargetFragment(parent, 0);
886 dialog.show(parent.getFragmentManager(), TAG_CYCLE_EDITOR);
887 }
888
889 @Override
890 public Dialog onCreateDialog(Bundle savedInstanceState) {
891 final Context context = getActivity();
892
893 final AlertDialog.Builder builder = new AlertDialog.Builder(context);
894 final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
895
896 final View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false);
897 final NumberPicker cycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day);
898
899 final int oldCycleDay = getArguments().getInt(EXTRA_CYCLE_DAY, 1);
900
901 cycleDayPicker.setMinValue(1);
902 cycleDayPicker.setMaxValue(31);
903 cycleDayPicker.setValue(oldCycleDay);
904 cycleDayPicker.setWrapSelectorWheel(true);
905
906 builder.setTitle(R.string.data_usage_cycle_editor_title);
907 builder.setView(view);
908
909 builder.setPositiveButton(R.string.data_usage_cycle_editor_positive,
910 new DialogInterface.OnClickListener() {
911 public void onClick(DialogInterface dialog, int which) {
912 final int cycleDay = cycleDayPicker.getValue();
913 final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
914 if (target != null) {
915 target.setPolicyCycleDay(cycleDay);
916 }
917 }
918 });
919
920 return builder.create();
921 }
922 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700923
Jeff Sharkey8e911d72011-06-14 22:41:21 -0700924 /**
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700925 * Dialog explaining that {@link NetworkPolicy#limitBytes} has been passed,
926 * and giving the user an option to bypass.
927 */
928 public static class PolicyLimitFragment extends DialogFragment {
929 public static final String EXTRA_TITLE_ID = "titleId";
930
931 public static void show(DataUsageSummary parent) {
932 final Bundle args = new Bundle();
933
Jeff Sharkeya662e492011-06-18 21:57:06 -0700934 switch (parent.mTemplate.getMatchRule()) {
935 case MATCH_MOBILE_3G_LOWER: {
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700936 args.putInt(EXTRA_TITLE_ID, R.string.data_usage_disabled_dialog_3g_title);
937 break;
938 }
Jeff Sharkeya662e492011-06-18 21:57:06 -0700939 case MATCH_MOBILE_4G: {
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700940 args.putInt(EXTRA_TITLE_ID, R.string.data_usage_disabled_dialog_4g_title);
941 break;
942 }
Jeff Sharkeya662e492011-06-18 21:57:06 -0700943 case MATCH_MOBILE_ALL: {
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700944 args.putInt(EXTRA_TITLE_ID, R.string.data_usage_disabled_dialog_mobile_title);
945 break;
946 }
947 }
948
949 final PolicyLimitFragment dialog = new PolicyLimitFragment();
950 dialog.setArguments(args);
951 dialog.setTargetFragment(parent, 0);
952 dialog.show(parent.getFragmentManager(), TAG_POLICY_LIMIT);
953 }
954
955 @Override
956 public Dialog onCreateDialog(Bundle savedInstanceState) {
957 final Context context = getActivity();
958
959 final int titleId = getArguments().getInt(EXTRA_TITLE_ID);
960
961 final AlertDialog.Builder builder = new AlertDialog.Builder(context);
962 builder.setTitle(titleId);
963 builder.setMessage(R.string.data_usage_disabled_dialog);
964
965 builder.setPositiveButton(android.R.string.ok, null);
966 builder.setNegativeButton(R.string.data_usage_disabled_dialog_enable,
967 new DialogInterface.OnClickListener() {
968 public void onClick(DialogInterface dialog, int which) {
969 final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
970 if (target != null) {
971 // TODO: consider "allow 100mb more data", or
972 // only bypass limit for current cycle.
973 target.setPolicyLimitBytes(LIMIT_DISABLED);
974 }
975 }
976 });
977
978 return builder.create();
979 }
980 }
981
982 /**
983 * Compute default tab that should be selected, based on
984 * {@link NetworkPolicyManager#EXTRA_NETWORK_TEMPLATE} extra.
985 */
986 private static String computeTabFromIntent(Intent intent) {
987 final int networkTemplate = intent.getIntExtra(EXTRA_NETWORK_TEMPLATE, TEMPLATE_INVALID);
988 switch (networkTemplate) {
Jeff Sharkeya662e492011-06-18 21:57:06 -0700989 case MATCH_MOBILE_3G_LOWER:
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700990 return TAB_3G;
Jeff Sharkeya662e492011-06-18 21:57:06 -0700991 case MATCH_MOBILE_4G:
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700992 return TAB_4G;
Jeff Sharkeya662e492011-06-18 21:57:06 -0700993 case MATCH_MOBILE_ALL:
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700994 return TAB_MOBILE;
Jeff Sharkeya662e492011-06-18 21:57:06 -0700995 case MATCH_WIFI:
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700996 return TAB_WIFI;
997 default:
998 return null;
999 }
1000 }
1001
1002 /**
Jeff Sharkey8e911d72011-06-14 22:41:21 -07001003 * Resolve best descriptive label for the given UID.
1004 */
1005 public static CharSequence resolveLabelForUid(PackageManager pm, int uid) {
1006 final String[] packageNames = pm.getPackagesForUid(uid);
1007 final int length = packageNames != null ? packageNames.length : 0;
1008
1009 CharSequence label = pm.getNameForUid(uid);
1010 try {
1011 if (length == 1) {
1012 final ApplicationInfo info = pm.getApplicationInfo(packageNames[0], 0);
1013 label = info.loadLabel(pm);
1014 } else if (length > 1) {
1015 for (String packageName : packageNames) {
1016 final PackageInfo info = pm.getPackageInfo(packageName, 0);
1017 if (info.sharedUserLabel != 0) {
1018 label = pm.getText(packageName, info.sharedUserLabel, info.applicationInfo);
1019 if (!TextUtils.isEmpty(label)) {
1020 break;
1021 }
1022 }
1023 }
1024 }
1025 } catch (NameNotFoundException e) {
1026 }
1027
1028 if (TextUtils.isEmpty(label)) {
1029 label = Integer.toString(uid);
1030 }
1031 return label;
1032 }
1033
Jeff Sharkey29d56b32011-06-20 17:06:52 -07001034 /**
1035 * Test if device has a mobile data radio.
1036 */
1037 private static boolean hasMobileRadio(Context context) {
1038 final ConnectivityManager conn = (ConnectivityManager) context.getSystemService(
1039 Context.CONNECTIVITY_SERVICE);
1040
1041 // mobile devices should have MOBILE network tracker regardless of
1042 // connection status.
1043 return conn.getNetworkInfo(TYPE_MOBILE) != null;
1044 }
1045
1046 /**
1047 * Test if device has a mobile 4G data radio.
1048 */
1049 private static boolean hasMobile4gRadio(Context context) {
1050 final ConnectivityManager conn = (ConnectivityManager) context.getSystemService(
1051 Context.CONNECTIVITY_SERVICE);
1052 final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
1053 Context.TELEPHONY_SERVICE);
1054
1055 // WiMAX devices should have WiMAX network tracker regardless of
1056 // connection status.
1057 final boolean hasWimax = conn.getNetworkInfo(TYPE_WIMAX) != null;
1058 final boolean hasLte = telephony.getLteOnCdmaMode() == Phone.LTE_ON_CDMA_TRUE;
1059 return hasWimax || hasLte;
1060 }
1061
1062 /**
1063 * Test if device has a Wi-Fi data radio.
1064 */
1065 private static boolean hasWifiRadio(Context context) {
1066 return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI);
1067 }
1068
1069 /**
1070 * Inflate a {@link Preference} style layout, adding the given {@link View}
1071 * widget into {@link android.R.id#widget_frame}.
1072 */
1073 private static View inflatePreference(LayoutInflater inflater, ViewGroup root, View widget) {
1074 final View view = inflater.inflate(R.layout.preference, root, false);
1075 final LinearLayout widgetFrame = (LinearLayout) view.findViewById(
1076 android.R.id.widget_frame);
1077 widgetFrame.addView(widget, new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
1078 return view;
1079 }
1080
1081 /**
1082 * Set {@link android.R.id#title} for a preference view inflated with
1083 * {@link #inflatePreference(LayoutInflater, View, View)}.
1084 */
1085 private static void setPreferenceTitle(View parent, int resId) {
1086 final TextView title = (TextView) parent.findViewById(android.R.id.title);
1087 title.setText(resId);
1088 }
1089
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -07001090}