blob: eff8e1718d48c1730dddff15c859ff9b6e58a755 [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 Sharkey05cc0cc2011-06-12 23:11:24 -070019import static android.net.NetworkPolicy.LIMIT_DISABLED;
Jeff Sharkey8a503642011-06-10 13:31:21 -070020import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
21import static android.net.NetworkPolicyManager.computeNextCycleBoundary;
22import static android.net.TrafficStats.TEMPLATE_MOBILE_3G_LOWER;
23import static android.net.TrafficStats.TEMPLATE_MOBILE_4G;
24import static android.net.TrafficStats.TEMPLATE_MOBILE_ALL;
25import static android.net.TrafficStats.TEMPLATE_WIFI;
26import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070027
Jeff Sharkey4c72ae52011-06-14 15:01:18 -070028import android.app.AlertDialog;
29import android.app.Dialog;
30import android.app.DialogFragment;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070031import android.app.Fragment;
32import android.content.Context;
Jeff Sharkey4c72ae52011-06-14 15:01:18 -070033import android.content.DialogInterface;
Jeff Sharkey4dfa6602011-06-13 00:42:03 -070034import android.content.Intent;
Jeff Sharkey8e911d72011-06-14 22:41:21 -070035import android.content.pm.ApplicationInfo;
36import android.content.pm.PackageInfo;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070037import android.content.pm.PackageManager;
Jeff Sharkey8e911d72011-06-14 22:41:21 -070038import android.content.pm.PackageManager.NameNotFoundException;
Jeff Sharkey8a503642011-06-10 13:31:21 -070039import android.net.INetworkPolicyManager;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070040import android.net.INetworkStatsService;
Jeff Sharkey8a503642011-06-10 13:31:21 -070041import android.net.NetworkPolicy;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070042import android.net.NetworkStats;
43import android.net.NetworkStatsHistory;
Jeff Sharkeyaa5260e2011-06-14 23:21:59 -070044import android.os.AsyncTask;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070045import android.os.Bundle;
46import android.os.RemoteException;
47import android.os.ServiceManager;
Jeff Sharkey8a503642011-06-10 13:31:21 -070048import android.preference.CheckBoxPreference;
49import android.preference.Preference;
Jeff Sharkey4dfa6602011-06-13 00:42:03 -070050import android.preference.PreferenceActivity;
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -070051import android.preference.SwitchPreference;
52import android.telephony.TelephonyManager;
Jeff Sharkey8e911d72011-06-14 22:41:21 -070053import android.text.TextUtils;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070054import android.text.format.DateUtils;
55import android.text.format.Formatter;
Jeff Sharkey8a503642011-06-10 13:31:21 -070056import android.text.format.Time;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070057import android.util.Log;
58import android.view.LayoutInflater;
Jeff Sharkey8a503642011-06-10 13:31:21 -070059import android.view.Menu;
60import android.view.MenuInflater;
61import android.view.MenuItem;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070062import android.view.View;
Jeff Sharkey8a503642011-06-10 13:31:21 -070063import android.view.View.OnClickListener;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070064import android.view.ViewGroup;
Jeff Sharkey8a503642011-06-10 13:31:21 -070065import android.widget.AbsListView;
66import android.widget.AdapterView;
67import android.widget.AdapterView.OnItemClickListener;
68import android.widget.AdapterView.OnItemSelectedListener;
69import android.widget.ArrayAdapter;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070070import android.widget.BaseAdapter;
Jeff Sharkey8a503642011-06-10 13:31:21 -070071import android.widget.LinearLayout;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070072import android.widget.ListView;
Jeff Sharkey4c72ae52011-06-14 15:01:18 -070073import android.widget.NumberPicker;
Jeff Sharkey8a503642011-06-10 13:31:21 -070074import android.widget.Spinner;
75import android.widget.TabHost;
76import android.widget.TabHost.OnTabChangeListener;
77import android.widget.TabHost.TabContentFactory;
78import android.widget.TabHost.TabSpec;
79import android.widget.TabWidget;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070080import android.widget.TextView;
81
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -070082import com.android.settings.net.NetworkPolicyModifier;
Jeff Sharkey8a503642011-06-10 13:31:21 -070083import com.android.settings.widget.DataUsageChartView;
84import com.android.settings.widget.DataUsageChartView.DataUsageChartListener;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070085import com.google.android.collect.Lists;
86
87import java.util.ArrayList;
Jeff Sharkey8a503642011-06-10 13:31:21 -070088import java.util.Arrays;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070089import java.util.Collections;
Jeff Sharkey8a503642011-06-10 13:31:21 -070090import java.util.Locale;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070091
92public class DataUsageSummary extends Fragment {
93 private static final String TAG = "DataUsage";
Jeff Sharkey8a503642011-06-10 13:31:21 -070094 private static final boolean LOGD = true;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070095
Jeff Sharkey8a503642011-06-10 13:31:21 -070096 private static final int TEMPLATE_INVALID = -1;
97
98 private static final String TAB_3G = "3g";
99 private static final String TAB_4G = "4g";
100 private static final String TAB_MOBILE = "mobile";
101 private static final String TAB_WIFI = "wifi";
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700102
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700103 private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
104 private static final String TAG_CYCLE_EDITOR = "cycleEditor";
105
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700106 private static final long KB_IN_BYTES = 1024;
107 private static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
108 private static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
109
110 private INetworkStatsService mStatsService;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700111 private INetworkPolicyManager mPolicyService;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700112
Jeff Sharkey8a503642011-06-10 13:31:21 -0700113 private TabHost mTabHost;
114 private TabWidget mTabWidget;
115 private ListView mListView;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700116 private DataUsageAdapter mAdapter;
117
Jeff Sharkey8a503642011-06-10 13:31:21 -0700118 private View mHeader;
119 private LinearLayout mSwitches;
120
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700121 private SwitchPreference mDataEnabled;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700122 private CheckBoxPreference mDisableAtLimit;
123 private View mDataEnabledView;
124 private View mDisableAtLimitView;
125
126 private DataUsageChartView mChart;
127
128 private Spinner mCycleSpinner;
129 private CycleAdapter mCycleAdapter;
130
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700131 // TODO: persist show wifi flag
Jeff Sharkey8a503642011-06-10 13:31:21 -0700132 private boolean mShowWifi = false;
133
134 private int mTemplate = TEMPLATE_INVALID;
135
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700136 private NetworkPolicyModifier mPolicyModifier;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700137 private NetworkStatsHistory mHistory;
138
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700139 @Override
Jeff Sharkey8a503642011-06-10 13:31:21 -0700140 public void onCreate(Bundle savedInstanceState) {
141 super.onCreate(savedInstanceState);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700142
143 mStatsService = INetworkStatsService.Stub.asInterface(
144 ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
Jeff Sharkey8a503642011-06-10 13:31:21 -0700145 mPolicyService = INetworkPolicyManager.Stub.asInterface(
146 ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700147
148 final Context context = getActivity();
149 final String subscriberId = getActiveSubscriberId(context);
150 mPolicyModifier = new NetworkPolicyModifier(mPolicyService, subscriberId);
Jeff Sharkey94a90952011-06-13 22:31:09 -0700151 mPolicyModifier.read();
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700152
153 setHasOptionsMenu(true);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700154 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700155
Jeff Sharkey8a503642011-06-10 13:31:21 -0700156 @Override
157 public View onCreateView(LayoutInflater inflater, ViewGroup container,
158 Bundle savedInstanceState) {
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700159
Jeff Sharkey8a503642011-06-10 13:31:21 -0700160 final Context context = inflater.getContext();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700161 final View view = inflater.inflate(R.layout.data_usage_summary, container, false);
162
Jeff Sharkey8a503642011-06-10 13:31:21 -0700163 mTabHost = (TabHost) view.findViewById(android.R.id.tabhost);
164 mTabWidget = (TabWidget) view.findViewById(android.R.id.tabs);
165 mListView = (ListView) view.findViewById(android.R.id.list);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700166
Jeff Sharkey8a503642011-06-10 13:31:21 -0700167 mTabHost.setup();
168 mTabHost.setOnTabChangedListener(mTabListener);
169
170 mHeader = inflater.inflate(R.layout.data_usage_header, mListView, false);
171 mListView.addHeaderView(mHeader, null, false);
172
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700173 mDataEnabled = new SwitchPreference(context);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700174 mDisableAtLimit = new CheckBoxPreference(context);
175
176 // kick refresh once to force-create views
177 refreshPreferenceViews();
178
179 // TODO: remove once thin preferences are supported (48dip)
180 mDataEnabledView.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, 72));
181 mDisableAtLimitView.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, 72));
182
183 mDataEnabledView.setOnClickListener(mDataEnabledListener);
184 mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener);
185
186 mSwitches = (LinearLayout) mHeader.findViewById(R.id.switches);
187 mSwitches.addView(mDataEnabledView);
188 mSwitches.addView(mDisableAtLimitView);
189
190 mCycleSpinner = (Spinner) mHeader.findViewById(R.id.cycles);
191 mCycleAdapter = new CycleAdapter(context);
192 mCycleSpinner.setAdapter(mCycleAdapter);
193 mCycleSpinner.setOnItemSelectedListener(mCycleListener);
194
195 mChart = new DataUsageChartView(context);
196 mChart.setListener(mChartListener);
197 mChart.setLayoutParams(new AbsListView.LayoutParams(MATCH_PARENT, 350));
198 mListView.addHeaderView(mChart, null, false);
199
200 mAdapter = new DataUsageAdapter();
201 mListView.setOnItemClickListener(mListListener);
202 mListView.setAdapter(mAdapter);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700203
204 return view;
205 }
206
207 @Override
208 public void onResume() {
209 super.onResume();
210
Jeff Sharkey8a503642011-06-10 13:31:21 -0700211 // this kicks off chain reaction which creates tabs, binds the body to
212 // selected network, and binds chart, cycles and detail list.
213 updateTabs();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700214 }
215
Jeff Sharkey8a503642011-06-10 13:31:21 -0700216 @Override
217 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
218 inflater.inflate(R.menu.data_usage, menu);
219 }
220
221 @Override
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700222 public void onPrepareOptionsMenu(Menu menu) {
223 final MenuItem split4g = menu.findItem(R.id.action_split_4g);
224 split4g.setChecked(mPolicyModifier.isMobilePolicySplit());
225 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700226
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700227 @Override
228 public boolean onOptionsItemSelected(MenuItem item) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700229 switch (item.getItemId()) {
230 case R.id.action_split_4g: {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700231 final boolean mobileSplit = !item.isChecked();
232 mPolicyModifier.setMobilePolicySplit(mobileSplit);
233 item.setChecked(mPolicyModifier.isMobilePolicySplit());
Jeff Sharkey8a503642011-06-10 13:31:21 -0700234 updateTabs();
235 return true;
236 }
237 case R.id.action_show_wifi: {
238 mShowWifi = !item.isChecked();
239 item.setChecked(mShowWifi);
240 updateTabs();
241 return true;
242 }
243 }
244 return false;
245 }
246
Jeff Sharkey94a90952011-06-13 22:31:09 -0700247 @Override
248 public void onDestroyView() {
249 super.onDestroyView();
250
251 mDataEnabledView = null;
252 mDisableAtLimitView = null;
253 }
254
Jeff Sharkey8a503642011-06-10 13:31:21 -0700255 /**
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700256 * Rebuild all tabs based on {@link NetworkPolicyModifier} and
257 * {@link #mShowWifi}, hiding the tabs entirely when applicable. Selects
258 * first tab, and kicks off a full rebind of body contents.
Jeff Sharkey8a503642011-06-10 13:31:21 -0700259 */
260 private void updateTabs() {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700261 final boolean mobileSplit = mPolicyModifier.isMobilePolicySplit();
262 final boolean tabsVisible = mobileSplit || mShowWifi;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700263 mTabWidget.setVisibility(tabsVisible ? View.VISIBLE : View.GONE);
264 mTabHost.clearAllTabs();
265
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700266 if (mobileSplit) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700267 mTabHost.addTab(buildTabSpec(TAB_3G, R.string.data_usage_tab_3g));
268 mTabHost.addTab(buildTabSpec(TAB_4G, R.string.data_usage_tab_4g));
269 }
270
271 if (mShowWifi) {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700272 if (!mobileSplit) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700273 mTabHost.addTab(buildTabSpec(TAB_MOBILE, R.string.data_usage_tab_mobile));
274 }
275 mTabHost.addTab(buildTabSpec(TAB_WIFI, R.string.data_usage_tab_wifi));
276 }
277
278 if (mTabWidget.getTabCount() > 0) {
279 // select first tab, which will kick off updateBody()
280 mTabHost.setCurrentTab(0);
281 } else {
282 // no tabs shown; update body manually
283 updateBody();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700284 }
285 }
286
Jeff Sharkey8a503642011-06-10 13:31:21 -0700287 /**
288 * Factory that provide empty {@link View} to make {@link TabHost} happy.
289 */
290 private TabContentFactory mEmptyTabContent = new TabContentFactory() {
291 /** {@inheritDoc} */
292 public View createTabContent(String tag) {
293 return new View(mTabHost.getContext());
294 }
295 };
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700296
Jeff Sharkey8a503642011-06-10 13:31:21 -0700297 /**
298 * Build {@link TabSpec} with thin indicator, and empty content.
299 */
300 private TabSpec buildTabSpec(String tag, int titleRes) {
301 final LayoutInflater inflater = LayoutInflater.from(mTabWidget.getContext());
302 final View indicator = inflater.inflate(
303 R.layout.tab_indicator_thin_holo, mTabWidget, false);
304 final TextView title = (TextView) indicator.findViewById(android.R.id.title);
305 title.setText(titleRes);
306 return mTabHost.newTabSpec(tag).setIndicator(indicator).setContent(mEmptyTabContent);
307 }
308
309 private OnTabChangeListener mTabListener = new OnTabChangeListener() {
310 /** {@inheritDoc} */
311 public void onTabChanged(String tabId) {
312 // user changed tab; update body
313 updateBody();
314 }
315 };
316
317 /**
318 * Update body content based on current tab. Loads
319 * {@link NetworkStatsHistory} and {@link NetworkPolicy} from system, and
320 * binds them to visible controls.
321 */
322 private void updateBody() {
323 final String tabTag = mTabHost.getCurrentTabTag();
324 final String currentTab = tabTag != null ? tabTag : TAB_MOBILE;
325
326 if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab);
327
328 if (TAB_WIFI.equals(currentTab)) {
329 // wifi doesn't have any controls
330 mDataEnabledView.setVisibility(View.GONE);
331 mDisableAtLimitView.setVisibility(View.GONE);
332 mTemplate = TEMPLATE_WIFI;
333
334 } else {
335 // make sure we show for non-wifi
336 mDataEnabledView.setVisibility(View.VISIBLE);
337 mDisableAtLimitView.setVisibility(View.VISIBLE);
338 }
339
340 if (TAB_MOBILE.equals(currentTab)) {
341 mDataEnabled.setTitle(R.string.data_usage_enable_mobile);
342 mDisableAtLimit.setTitle(R.string.data_usage_disable_mobile_limit);
343 mTemplate = TEMPLATE_MOBILE_ALL;
344
345 } else if (TAB_3G.equals(currentTab)) {
346 mDataEnabled.setTitle(R.string.data_usage_enable_3g);
347 mDisableAtLimit.setTitle(R.string.data_usage_disable_3g_limit);
348 mTemplate = TEMPLATE_MOBILE_3G_LOWER;
349
350 } else if (TAB_4G.equals(currentTab)) {
351 mDataEnabled.setTitle(R.string.data_usage_enable_4g);
352 mDisableAtLimit.setTitle(R.string.data_usage_disable_4g_limit);
353 mTemplate = TEMPLATE_MOBILE_4G;
354
355 }
356
357 // TODO: populate checkbox based on radio preferences
358 mDataEnabled.setChecked(true);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700359
360 try {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700361 // load stats for current template
Jeff Sharkey8a503642011-06-10 13:31:21 -0700362 mHistory = mStatsService.getHistoryForNetwork(mTemplate);
363 } catch (RemoteException e) {
364 // since we can't do much without policy or history, and we don't
365 // want to leave with half-baked UI, we bail hard.
366 throw new RuntimeException("problem reading network policy or stats", e);
367 }
368
Jeff Sharkey8a503642011-06-10 13:31:21 -0700369 // bind chart to historical stats
Jeff Sharkey8a503642011-06-10 13:31:21 -0700370 mChart.bindNetworkStats(mHistory);
371
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700372 updatePolicy(true);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700373
374 // force scroll to top of body
375 mListView.smoothScrollToPosition(0);
376
377 // kick preference views so they rebind from changes above
378 refreshPreferenceViews();
379 }
380
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700381 private void setPolicyCycleDay(int cycleDay) {
382 if (LOGD) Log.d(TAG, "setPolicyCycleDay()");
383 mPolicyModifier.setPolicyCycleDay(mTemplate, cycleDay);
384 updatePolicy(true);
385 }
386
387 private void setPolicyWarningBytes(long warningBytes) {
388 if (LOGD) Log.d(TAG, "setPolicyWarningBytes()");
389 mPolicyModifier.setPolicyWarningBytes(mTemplate, warningBytes);
390 updatePolicy(false);
391 }
392
393 private void setPolicyLimitBytes(long limitBytes) {
394 if (LOGD) Log.d(TAG, "setPolicyLimitBytes()");
395 mPolicyModifier.setPolicyLimitBytes(mTemplate, limitBytes);
396 updatePolicy(false);
397 }
398
Jeff Sharkey8a503642011-06-10 13:31:21 -0700399 /**
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700400 * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for
401 * current {@link #mTemplate}.
402 */
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700403 private void updatePolicy(boolean refreshCycle) {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700404 final NetworkPolicy policy = mPolicyModifier.getPolicy(mTemplate);
405
406 // reflect policy limit in checkbox
407 mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED);
408 mChart.bindNetworkPolicy(policy);
409
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700410 if (refreshCycle) {
411 // generate cycle list based on policy and available history
412 updateCycleList(policy);
413 }
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700414
415 // kick preference views so they rebind from changes above
416 refreshPreferenceViews();
417 }
418
419 /**
Jeff Sharkey8a503642011-06-10 13:31:21 -0700420 * Return full time bounds (earliest and latest time recorded) of the given
421 * {@link NetworkStatsHistory}.
422 */
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700423 public static long[] getHistoryBounds(NetworkStatsHistory history) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700424 final long currentTime = System.currentTimeMillis();
425
426 long start = currentTime;
427 long end = currentTime;
428 if (history.bucketCount > 0) {
429 start = history.bucketStart[0];
430 end = history.bucketStart[history.bucketCount - 1];
431 }
432
433 return new long[] { start, end };
434 }
435
436 /**
437 * Rebuild {@link #mCycleAdapter} based on {@link NetworkPolicy#cycleDay}
438 * and available {@link NetworkStatsHistory} data. Always selects the newest
439 * item, updating the inspection range on {@link #mChart}.
440 */
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700441 private void updateCycleList(NetworkPolicy policy) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700442 mCycleAdapter.clear();
443
444 final Context context = mCycleSpinner.getContext();
445
446 final long[] bounds = getHistoryBounds(mHistory);
447 final long historyStart = bounds[0];
448 final long historyEnd = bounds[1];
449
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700450 if (policy != null) {
451 // find the next cycle boundary
452 long cycleEnd = computeNextCycleBoundary(historyEnd, policy);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700453
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700454 int guardCount = 0;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700455
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700456 // walk backwards, generating all valid cycle ranges
457 while (cycleEnd > historyStart) {
458 final long cycleStart = computeLastCycleBoundary(cycleEnd, policy);
459 Log.d(TAG, "generating cs=" + cycleStart + " to ce=" + cycleEnd + " waiting for hs="
460 + historyStart);
461 mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd));
462 cycleEnd = cycleStart;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700463
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700464 // TODO: remove this guard once we have better testing
465 if (guardCount++ > 50) {
466 Log.wtf(TAG, "stuck generating ranges for bounds=" + Arrays.toString(bounds)
467 + " and policy=" + policy);
468 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700469 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700470
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700471 // one last cycle entry to modify policy cycle day
472 mCycleAdapter.add(new CycleChangeItem(context));
473
474 } else {
475 // no valid cycle; show all data
476 // TODO: offer simple ranges like "last week" etc
477 mCycleAdapter.add(new CycleItem(context, historyStart, historyEnd));
478
479 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700480
481 // force pick the current cycle (first item)
482 mCycleSpinner.setSelection(0);
483 mCycleListener.onItemSelected(mCycleSpinner, null, 0, 0);
484 }
485
486 /**
487 * Force rebind of hijacked {@link Preference} views.
488 */
489 private void refreshPreferenceViews() {
490 mDataEnabledView = mDataEnabled.getView(mDataEnabledView, mListView);
491 mDisableAtLimitView = mDisableAtLimit.getView(mDisableAtLimitView, mListView);
492 }
493
494 private OnClickListener mDataEnabledListener = new OnClickListener() {
495 /** {@inheritDoc} */
496 public void onClick(View v) {
497 mDataEnabled.setChecked(!mDataEnabled.isChecked());
498 refreshPreferenceViews();
499
500 // TODO: wire up to telephony to enable/disable radios
501 }
502 };
503
504 private OnClickListener mDisableAtLimitListener = new OnClickListener() {
505 /** {@inheritDoc} */
506 public void onClick(View v) {
507 final boolean disableAtLimit = !mDisableAtLimit.isChecked();
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700508 if (disableAtLimit) {
509 // enabling limit; show confirmation dialog which eventually
510 // calls setPolicyLimitBytes() once user confirms.
511 ConfirmLimitFragment.show(DataUsageSummary.this);
512 } else {
513 setPolicyLimitBytes(LIMIT_DISABLED);
514 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700515 }
516 };
517
518 private OnItemClickListener mListListener = new OnItemClickListener() {
519 /** {@inheritDoc} */
520 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700521 final AppUsageItem app = (AppUsageItem) parent.getItemAtPosition(position);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700522
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700523 final Bundle args = new Bundle();
524 args.putInt(Intent.EXTRA_UID, app.uid);
525
526 final PreferenceActivity activity = (PreferenceActivity) getActivity();
527 activity.startPreferencePanel(DataUsageAppDetail.class.getName(), args,
528 R.string.data_usage_summary_title, null, null, 0);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700529 }
530 };
531
532 private OnItemSelectedListener mCycleListener = new OnItemSelectedListener() {
533 /** {@inheritDoc} */
534 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
535 final CycleItem cycle = (CycleItem) parent.getItemAtPosition(position);
536 if (cycle instanceof CycleChangeItem) {
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700537 // show cycle editor; will eventually call setPolicyCycleDay()
538 // when user finishes editing.
539 CycleEditorFragment.show(DataUsageSummary.this);
540
541 // reset spinner to something other than "change cycle..."
542 mCycleSpinner.setSelection(0);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700543
544 } else {
Jeff Sharkey8e911d72011-06-14 22:41:21 -0700545 if (LOGD) {
546 Log.d(TAG, "showing cycle " + cycle + ", start=" + cycle.start + ", end="
547 + cycle.end + "]");
548 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700549
550 // update chart to show selected cycle, and update detail data
551 // to match updated sweep bounds.
552 final long[] bounds = getHistoryBounds(mHistory);
553 mChart.setVisibleRange(cycle.start, cycle.end, bounds[1]);
554
555 updateDetailData();
556 }
557 }
558
559 /** {@inheritDoc} */
560 public void onNothingSelected(AdapterView<?> parent) {
561 // ignored
562 }
563 };
564
565 /**
566 * Update {@link #mAdapter} with sorted list of applications data usage,
567 * based on current inspection from {@link #mChart}.
568 */
569 private void updateDetailData() {
570 if (LOGD) Log.d(TAG, "updateDetailData()");
571
Jeff Sharkeyaa5260e2011-06-14 23:21:59 -0700572 new AsyncTask<Void, Void, NetworkStats>() {
573 @Override
574 protected NetworkStats doInBackground(Void... params) {
575 try {
576 final long[] range = mChart.getInspectRange();
577 return mStatsService.getSummaryForAllUid(range[0], range[1], mTemplate);
578 } catch (RemoteException e) {
579 Log.w(TAG, "problem reading stats");
580 }
581 return null;
582 }
583
584 @Override
585 protected void onPostExecute(NetworkStats stats) {
586 if (stats != null) {
587 mAdapter.bindStats(stats);
588 }
589 }
590 }.execute();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700591 }
592
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700593 private static String getActiveSubscriberId(Context context) {
594 final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
595 Context.TELEPHONY_SERVICE);
596 return telephony.getSubscriberId();
597 }
598
Jeff Sharkey8a503642011-06-10 13:31:21 -0700599 private DataUsageChartListener mChartListener = new DataUsageChartListener() {
600 /** {@inheritDoc} */
601 public void onInspectRangeChanged() {
602 if (LOGD) Log.d(TAG, "onInspectRangeChanged()");
603 updateDetailData();
604 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700605
Jeff Sharkey8a503642011-06-10 13:31:21 -0700606 /** {@inheritDoc} */
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700607 public void onWarningChanged() {
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700608 setPolicyWarningBytes(mChart.getWarningBytes());
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700609 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700610
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700611 /** {@inheritDoc} */
612 public void onLimitChanged() {
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700613 setPolicyLimitBytes(mChart.getLimitBytes());
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700614 }
615 };
616
617
618 /**
Jeff Sharkey8a503642011-06-10 13:31:21 -0700619 * List item that reflects a specific data usage cycle.
620 */
621 public static class CycleItem {
622 public CharSequence label;
623 public long start;
624 public long end;
625
626 private static final StringBuilder sBuilder = new StringBuilder(50);
627 private static final java.util.Formatter sFormatter = new java.util.Formatter(
628 sBuilder, Locale.getDefault());
629
630 CycleItem(CharSequence label) {
631 this.label = label;
632 }
633
634 public CycleItem(Context context, long start, long end) {
635 this.label = formatDateRangeUtc(context, start, end);
636 this.start = start;
637 this.end = end;
638 }
639
640 private static String formatDateRangeUtc(Context context, long start, long end) {
641 synchronized (sBuilder) {
642 sBuilder.setLength(0);
643 return DateUtils.formatDateRange(context, sFormatter, start, end,
644 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH,
645 Time.TIMEZONE_UTC).toString();
646 }
647 }
648
649 @Override
650 public String toString() {
651 return label.toString();
652 }
653 }
654
655 /**
656 * Special-case data usage cycle that triggers dialog to change
657 * {@link NetworkPolicy#cycleDay}.
658 */
659 public static class CycleChangeItem extends CycleItem {
660 public CycleChangeItem(Context context) {
661 super(context.getString(R.string.data_usage_change_cycle));
662 }
663 }
664
665 public static class CycleAdapter extends ArrayAdapter<CycleItem> {
666 public CycleAdapter(Context context) {
667 super(context, android.R.layout.simple_spinner_item);
668 setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
669 }
670 }
671
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700672 private static class AppUsageItem implements Comparable<AppUsageItem> {
673 public int uid;
674 public long total;
675
676 /** {@inheritDoc} */
677 public int compareTo(AppUsageItem another) {
678 return Long.compare(another.total, total);
679 }
680 }
681
Jeff Sharkey8a503642011-06-10 13:31:21 -0700682 /**
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700683 * Adapter of applications, sorted by total usage descending.
684 */
685 public static class DataUsageAdapter extends BaseAdapter {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700686 private ArrayList<AppUsageItem> mItems = Lists.newArrayList();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700687
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700688 public void bindStats(NetworkStats stats) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700689 mItems.clear();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700690
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700691 for (int i = 0; i < stats.size; i++) {
Jeff Sharkey8e911d72011-06-14 22:41:21 -0700692 final long total = stats.rx[i] + stats.tx[i];
693 if (total > 0) {
694 final AppUsageItem item = new AppUsageItem();
695 item.uid = stats.uid[i];
696 item.total = total;
697 mItems.add(item);
698 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700699 }
700
Jeff Sharkey8a503642011-06-10 13:31:21 -0700701 Collections.sort(mItems);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700702 notifyDataSetChanged();
703 }
704
705 @Override
706 public int getCount() {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700707 return mItems.size();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700708 }
709
710 @Override
711 public Object getItem(int position) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700712 return mItems.get(position);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700713 }
714
715 @Override
716 public long getItemId(int position) {
717 return position;
718 }
719
720 @Override
721 public View getView(int position, View convertView, ViewGroup parent) {
722 if (convertView == null) {
723 convertView = LayoutInflater.from(parent.getContext()).inflate(
724 android.R.layout.simple_list_item_2, parent, false);
725 }
726
727 final Context context = parent.getContext();
728 final PackageManager pm = context.getPackageManager();
729
730 final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
731 final TextView text2 = (TextView) convertView.findViewById(android.R.id.text2);
732
Jeff Sharkey8a503642011-06-10 13:31:21 -0700733 final AppUsageItem item = mItems.get(position);
Jeff Sharkey8e911d72011-06-14 22:41:21 -0700734 text1.setText(resolveLabelForUid(pm, item.uid));
Jeff Sharkey8a503642011-06-10 13:31:21 -0700735 text2.setText(Formatter.formatFileSize(context, item.total));
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700736
737 return convertView;
738 }
739
740 }
741
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700742 /**
743 * Dialog to request user confirmation before setting
744 * {@link NetworkPolicy#limitBytes}.
745 */
746 public static class ConfirmLimitFragment extends DialogFragment {
747 public static final String EXTRA_MESSAGE_ID = "messageId";
748 public static final String EXTRA_LIMIT_BYTES = "limitBytes";
749
750 public static void show(DataUsageSummary parent) {
751 final Bundle args = new Bundle();
752
753 // TODO: customize default limits based on network template
754 switch (parent.mTemplate) {
755 case TEMPLATE_MOBILE_3G_LOWER: {
756 args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_3g);
757 args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES);
758 break;
759 }
760 case TEMPLATE_MOBILE_4G: {
761 args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_4g);
762 args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES);
763 break;
764 }
765 case TEMPLATE_MOBILE_ALL: {
766 args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_mobile);
767 args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES);
768 break;
769 }
770 }
771
772 final ConfirmLimitFragment dialog = new ConfirmLimitFragment();
773 dialog.setArguments(args);
774 dialog.setTargetFragment(parent, 0);
775 dialog.show(parent.getFragmentManager(), TAG_CONFIRM_LIMIT);
776 }
777
778 @Override
779 public Dialog onCreateDialog(Bundle savedInstanceState) {
780 final Context context = getActivity();
781
782 final int messageId = getArguments().getInt(EXTRA_MESSAGE_ID);
783 final long limitBytes = getArguments().getLong(EXTRA_LIMIT_BYTES);
784
785 final AlertDialog.Builder builder = new AlertDialog.Builder(context);
786 builder.setTitle(R.string.data_usage_limit_dialog_title);
787 builder.setMessage(messageId);
788
789 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
790 public void onClick(DialogInterface dialog, int which) {
791 final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
792 if (target != null) {
793 target.setPolicyLimitBytes(limitBytes);
794 }
795 }
796 });
797
798 return builder.create();
799 }
800 }
801
802 /**
803 * Dialog to edit {@link NetworkPolicy#cycleDay}.
804 */
805 public static class CycleEditorFragment extends DialogFragment {
806 public static final String EXTRA_CYCLE_DAY = "cycleDay";
807
808 public static void show(DataUsageSummary parent) {
809 final NetworkPolicy policy = parent.mPolicyModifier.getPolicy(parent.mTemplate);
810 final Bundle args = new Bundle();
811 args.putInt(CycleEditorFragment.EXTRA_CYCLE_DAY, policy.cycleDay);
812
813 final CycleEditorFragment dialog = new CycleEditorFragment();
814 dialog.setArguments(args);
815 dialog.setTargetFragment(parent, 0);
816 dialog.show(parent.getFragmentManager(), TAG_CYCLE_EDITOR);
817 }
818
819 @Override
820 public Dialog onCreateDialog(Bundle savedInstanceState) {
821 final Context context = getActivity();
822
823 final AlertDialog.Builder builder = new AlertDialog.Builder(context);
824 final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
825
826 final View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false);
827 final NumberPicker cycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day);
828
829 final int oldCycleDay = getArguments().getInt(EXTRA_CYCLE_DAY, 1);
830
831 cycleDayPicker.setMinValue(1);
832 cycleDayPicker.setMaxValue(31);
833 cycleDayPicker.setValue(oldCycleDay);
834 cycleDayPicker.setWrapSelectorWheel(true);
835
836 builder.setTitle(R.string.data_usage_cycle_editor_title);
837 builder.setView(view);
838
839 builder.setPositiveButton(R.string.data_usage_cycle_editor_positive,
840 new DialogInterface.OnClickListener() {
841 public void onClick(DialogInterface dialog, int which) {
842 final int cycleDay = cycleDayPicker.getValue();
843 final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
844 if (target != null) {
845 target.setPolicyCycleDay(cycleDay);
846 }
847 }
848 });
849
850 return builder.create();
851 }
852 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700853
Jeff Sharkey8e911d72011-06-14 22:41:21 -0700854 /**
855 * Resolve best descriptive label for the given UID.
856 */
857 public static CharSequence resolveLabelForUid(PackageManager pm, int uid) {
858 final String[] packageNames = pm.getPackagesForUid(uid);
859 final int length = packageNames != null ? packageNames.length : 0;
860
861 CharSequence label = pm.getNameForUid(uid);
862 try {
863 if (length == 1) {
864 final ApplicationInfo info = pm.getApplicationInfo(packageNames[0], 0);
865 label = info.loadLabel(pm);
866 } else if (length > 1) {
867 for (String packageName : packageNames) {
868 final PackageInfo info = pm.getPackageInfo(packageName, 0);
869 if (info.sharedUserLabel != 0) {
870 label = pm.getText(packageName, info.sharedUserLabel, info.applicationInfo);
871 if (!TextUtils.isEmpty(label)) {
872 break;
873 }
874 }
875 }
876 }
877 } catch (NameNotFoundException e) {
878 }
879
880 if (TextUtils.isEmpty(label)) {
881 label = Integer.toString(uid);
882 }
883 return label;
884 }
885
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700886}