blob: 44a86dfdeb0452aae858bf22685ea9beb8b40ff2 [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
28import android.app.Fragment;
29import android.content.Context;
30import android.content.pm.PackageManager;
Jeff Sharkey8a503642011-06-10 13:31:21 -070031import android.net.INetworkPolicyManager;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070032import android.net.INetworkStatsService;
Jeff Sharkey8a503642011-06-10 13:31:21 -070033import android.net.NetworkPolicy;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070034import android.net.NetworkStats;
35import android.net.NetworkStatsHistory;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070036import android.os.Bundle;
37import android.os.RemoteException;
38import android.os.ServiceManager;
Jeff Sharkey8a503642011-06-10 13:31:21 -070039import android.preference.CheckBoxPreference;
40import android.preference.Preference;
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -070041import android.preference.SwitchPreference;
42import android.telephony.TelephonyManager;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070043import android.text.format.DateUtils;
44import android.text.format.Formatter;
Jeff Sharkey8a503642011-06-10 13:31:21 -070045import android.text.format.Time;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070046import android.util.Log;
47import android.view.LayoutInflater;
Jeff Sharkey8a503642011-06-10 13:31:21 -070048import android.view.Menu;
49import android.view.MenuInflater;
50import android.view.MenuItem;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070051import android.view.View;
Jeff Sharkey8a503642011-06-10 13:31:21 -070052import android.view.View.OnClickListener;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070053import android.view.ViewGroup;
Jeff Sharkey8a503642011-06-10 13:31:21 -070054import android.widget.AbsListView;
55import android.widget.AdapterView;
56import android.widget.AdapterView.OnItemClickListener;
57import android.widget.AdapterView.OnItemSelectedListener;
58import android.widget.ArrayAdapter;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070059import android.widget.BaseAdapter;
Jeff Sharkey8a503642011-06-10 13:31:21 -070060import android.widget.LinearLayout;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070061import android.widget.ListView;
Jeff Sharkey8a503642011-06-10 13:31:21 -070062import android.widget.Spinner;
63import android.widget.TabHost;
64import android.widget.TabHost.OnTabChangeListener;
65import android.widget.TabHost.TabContentFactory;
66import android.widget.TabHost.TabSpec;
67import android.widget.TabWidget;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070068import android.widget.TextView;
69
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -070070import com.android.settings.net.NetworkPolicyModifier;
Jeff Sharkey8a503642011-06-10 13:31:21 -070071import com.android.settings.widget.DataUsageChartView;
72import com.android.settings.widget.DataUsageChartView.DataUsageChartListener;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070073import com.google.android.collect.Lists;
74
75import java.util.ArrayList;
Jeff Sharkey8a503642011-06-10 13:31:21 -070076import java.util.Arrays;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070077import java.util.Collections;
Jeff Sharkey8a503642011-06-10 13:31:21 -070078import java.util.Locale;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070079
80public class DataUsageSummary extends Fragment {
81 private static final String TAG = "DataUsage";
Jeff Sharkey8a503642011-06-10 13:31:21 -070082 private static final boolean LOGD = true;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070083
Jeff Sharkey8a503642011-06-10 13:31:21 -070084 private static final int TEMPLATE_INVALID = -1;
85
86 private static final String TAB_3G = "3g";
87 private static final String TAB_4G = "4g";
88 private static final String TAB_MOBILE = "mobile";
89 private static final String TAB_WIFI = "wifi";
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070090
91 private static final long KB_IN_BYTES = 1024;
92 private static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
93 private static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
94
95 private INetworkStatsService mStatsService;
Jeff Sharkey8a503642011-06-10 13:31:21 -070096 private INetworkPolicyManager mPolicyService;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070097
Jeff Sharkey8a503642011-06-10 13:31:21 -070098 private TabHost mTabHost;
99 private TabWidget mTabWidget;
100 private ListView mListView;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700101 private DataUsageAdapter mAdapter;
102
Jeff Sharkey8a503642011-06-10 13:31:21 -0700103 private View mHeader;
104 private LinearLayout mSwitches;
105
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700106 private SwitchPreference mDataEnabled;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700107 private CheckBoxPreference mDisableAtLimit;
108 private View mDataEnabledView;
109 private View mDisableAtLimitView;
110
111 private DataUsageChartView mChart;
112
113 private Spinner mCycleSpinner;
114 private CycleAdapter mCycleAdapter;
115
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700116 // TODO: persist show wifi flag
Jeff Sharkey8a503642011-06-10 13:31:21 -0700117 private boolean mShowWifi = false;
118
119 private int mTemplate = TEMPLATE_INVALID;
120
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700121 private NetworkPolicyModifier mPolicyModifier;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700122 private NetworkStatsHistory mHistory;
123
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700124 @Override
Jeff Sharkey8a503642011-06-10 13:31:21 -0700125 public void onCreate(Bundle savedInstanceState) {
126 super.onCreate(savedInstanceState);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700127
128 mStatsService = INetworkStatsService.Stub.asInterface(
129 ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
Jeff Sharkey8a503642011-06-10 13:31:21 -0700130 mPolicyService = INetworkPolicyManager.Stub.asInterface(
131 ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700132
133 final Context context = getActivity();
134 final String subscriberId = getActiveSubscriberId(context);
135 mPolicyModifier = new NetworkPolicyModifier(mPolicyService, subscriberId);
136
137 setHasOptionsMenu(true);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700138 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700139
Jeff Sharkey8a503642011-06-10 13:31:21 -0700140 @Override
141 public View onCreateView(LayoutInflater inflater, ViewGroup container,
142 Bundle savedInstanceState) {
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700143
Jeff Sharkey8a503642011-06-10 13:31:21 -0700144 final Context context = inflater.getContext();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700145 final View view = inflater.inflate(R.layout.data_usage_summary, container, false);
146
Jeff Sharkey8a503642011-06-10 13:31:21 -0700147 mTabHost = (TabHost) view.findViewById(android.R.id.tabhost);
148 mTabWidget = (TabWidget) view.findViewById(android.R.id.tabs);
149 mListView = (ListView) view.findViewById(android.R.id.list);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700150
Jeff Sharkey8a503642011-06-10 13:31:21 -0700151 mTabHost.setup();
152 mTabHost.setOnTabChangedListener(mTabListener);
153
154 mHeader = inflater.inflate(R.layout.data_usage_header, mListView, false);
155 mListView.addHeaderView(mHeader, null, false);
156
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700157 mDataEnabled = new SwitchPreference(context);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700158 mDisableAtLimit = new CheckBoxPreference(context);
159
160 // kick refresh once to force-create views
161 refreshPreferenceViews();
162
163 // TODO: remove once thin preferences are supported (48dip)
164 mDataEnabledView.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, 72));
165 mDisableAtLimitView.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, 72));
166
167 mDataEnabledView.setOnClickListener(mDataEnabledListener);
168 mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener);
169
170 mSwitches = (LinearLayout) mHeader.findViewById(R.id.switches);
171 mSwitches.addView(mDataEnabledView);
172 mSwitches.addView(mDisableAtLimitView);
173
174 mCycleSpinner = (Spinner) mHeader.findViewById(R.id.cycles);
175 mCycleAdapter = new CycleAdapter(context);
176 mCycleSpinner.setAdapter(mCycleAdapter);
177 mCycleSpinner.setOnItemSelectedListener(mCycleListener);
178
179 mChart = new DataUsageChartView(context);
180 mChart.setListener(mChartListener);
181 mChart.setLayoutParams(new AbsListView.LayoutParams(MATCH_PARENT, 350));
182 mListView.addHeaderView(mChart, null, false);
183
184 mAdapter = new DataUsageAdapter();
185 mListView.setOnItemClickListener(mListListener);
186 mListView.setAdapter(mAdapter);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700187
188 return view;
189 }
190
191 @Override
192 public void onResume() {
193 super.onResume();
194
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700195 // read current policy state from service
196 mPolicyModifier.read();
197
Jeff Sharkey8a503642011-06-10 13:31:21 -0700198 // this kicks off chain reaction which creates tabs, binds the body to
199 // selected network, and binds chart, cycles and detail list.
200 updateTabs();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700201 }
202
Jeff Sharkey8a503642011-06-10 13:31:21 -0700203 @Override
204 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
205 inflater.inflate(R.menu.data_usage, menu);
206 }
207
208 @Override
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700209 public void onPrepareOptionsMenu(Menu menu) {
210 final MenuItem split4g = menu.findItem(R.id.action_split_4g);
211 split4g.setChecked(mPolicyModifier.isMobilePolicySplit());
212 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700213
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700214 @Override
215 public boolean onOptionsItemSelected(MenuItem item) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700216 switch (item.getItemId()) {
217 case R.id.action_split_4g: {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700218 final boolean mobileSplit = !item.isChecked();
219 mPolicyModifier.setMobilePolicySplit(mobileSplit);
220 item.setChecked(mPolicyModifier.isMobilePolicySplit());
Jeff Sharkey8a503642011-06-10 13:31:21 -0700221 updateTabs();
222 return true;
223 }
224 case R.id.action_show_wifi: {
225 mShowWifi = !item.isChecked();
226 item.setChecked(mShowWifi);
227 updateTabs();
228 return true;
229 }
230 }
231 return false;
232 }
233
234 /**
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700235 * Rebuild all tabs based on {@link NetworkPolicyModifier} and
236 * {@link #mShowWifi}, hiding the tabs entirely when applicable. Selects
237 * first tab, and kicks off a full rebind of body contents.
Jeff Sharkey8a503642011-06-10 13:31:21 -0700238 */
239 private void updateTabs() {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700240 final boolean mobileSplit = mPolicyModifier.isMobilePolicySplit();
241 final boolean tabsVisible = mobileSplit || mShowWifi;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700242 mTabWidget.setVisibility(tabsVisible ? View.VISIBLE : View.GONE);
243 mTabHost.clearAllTabs();
244
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700245 if (mobileSplit) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700246 mTabHost.addTab(buildTabSpec(TAB_3G, R.string.data_usage_tab_3g));
247 mTabHost.addTab(buildTabSpec(TAB_4G, R.string.data_usage_tab_4g));
248 }
249
250 if (mShowWifi) {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700251 if (!mobileSplit) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700252 mTabHost.addTab(buildTabSpec(TAB_MOBILE, R.string.data_usage_tab_mobile));
253 }
254 mTabHost.addTab(buildTabSpec(TAB_WIFI, R.string.data_usage_tab_wifi));
255 }
256
257 if (mTabWidget.getTabCount() > 0) {
258 // select first tab, which will kick off updateBody()
259 mTabHost.setCurrentTab(0);
260 } else {
261 // no tabs shown; update body manually
262 updateBody();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700263 }
264 }
265
Jeff Sharkey8a503642011-06-10 13:31:21 -0700266 /**
267 * Factory that provide empty {@link View} to make {@link TabHost} happy.
268 */
269 private TabContentFactory mEmptyTabContent = new TabContentFactory() {
270 /** {@inheritDoc} */
271 public View createTabContent(String tag) {
272 return new View(mTabHost.getContext());
273 }
274 };
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700275
Jeff Sharkey8a503642011-06-10 13:31:21 -0700276 /**
277 * Build {@link TabSpec} with thin indicator, and empty content.
278 */
279 private TabSpec buildTabSpec(String tag, int titleRes) {
280 final LayoutInflater inflater = LayoutInflater.from(mTabWidget.getContext());
281 final View indicator = inflater.inflate(
282 R.layout.tab_indicator_thin_holo, mTabWidget, false);
283 final TextView title = (TextView) indicator.findViewById(android.R.id.title);
284 title.setText(titleRes);
285 return mTabHost.newTabSpec(tag).setIndicator(indicator).setContent(mEmptyTabContent);
286 }
287
288 private OnTabChangeListener mTabListener = new OnTabChangeListener() {
289 /** {@inheritDoc} */
290 public void onTabChanged(String tabId) {
291 // user changed tab; update body
292 updateBody();
293 }
294 };
295
296 /**
297 * Update body content based on current tab. Loads
298 * {@link NetworkStatsHistory} and {@link NetworkPolicy} from system, and
299 * binds them to visible controls.
300 */
301 private void updateBody() {
302 final String tabTag = mTabHost.getCurrentTabTag();
303 final String currentTab = tabTag != null ? tabTag : TAB_MOBILE;
304
305 if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab);
306
307 if (TAB_WIFI.equals(currentTab)) {
308 // wifi doesn't have any controls
309 mDataEnabledView.setVisibility(View.GONE);
310 mDisableAtLimitView.setVisibility(View.GONE);
311 mTemplate = TEMPLATE_WIFI;
312
313 } else {
314 // make sure we show for non-wifi
315 mDataEnabledView.setVisibility(View.VISIBLE);
316 mDisableAtLimitView.setVisibility(View.VISIBLE);
317 }
318
319 if (TAB_MOBILE.equals(currentTab)) {
320 mDataEnabled.setTitle(R.string.data_usage_enable_mobile);
321 mDisableAtLimit.setTitle(R.string.data_usage_disable_mobile_limit);
322 mTemplate = TEMPLATE_MOBILE_ALL;
323
324 } else if (TAB_3G.equals(currentTab)) {
325 mDataEnabled.setTitle(R.string.data_usage_enable_3g);
326 mDisableAtLimit.setTitle(R.string.data_usage_disable_3g_limit);
327 mTemplate = TEMPLATE_MOBILE_3G_LOWER;
328
329 } else if (TAB_4G.equals(currentTab)) {
330 mDataEnabled.setTitle(R.string.data_usage_enable_4g);
331 mDisableAtLimit.setTitle(R.string.data_usage_disable_4g_limit);
332 mTemplate = TEMPLATE_MOBILE_4G;
333
334 }
335
336 // TODO: populate checkbox based on radio preferences
337 mDataEnabled.setChecked(true);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700338
339 try {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700340 // load stats for current template
Jeff Sharkey8a503642011-06-10 13:31:21 -0700341 mHistory = mStatsService.getHistoryForNetwork(mTemplate);
342 } catch (RemoteException e) {
343 // since we can't do much without policy or history, and we don't
344 // want to leave with half-baked UI, we bail hard.
345 throw new RuntimeException("problem reading network policy or stats", e);
346 }
347
Jeff Sharkey8a503642011-06-10 13:31:21 -0700348 // bind chart to historical stats
Jeff Sharkey8a503642011-06-10 13:31:21 -0700349 mChart.bindNetworkStats(mHistory);
350
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700351 updatePolicy();
Jeff Sharkey8a503642011-06-10 13:31:21 -0700352
353 // force scroll to top of body
354 mListView.smoothScrollToPosition(0);
355
356 // kick preference views so they rebind from changes above
357 refreshPreferenceViews();
358 }
359
360 /**
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700361 * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for
362 * current {@link #mTemplate}.
363 */
364 private void updatePolicy() {
365 final NetworkPolicy policy = mPolicyModifier.getPolicy(mTemplate);
366
367 // reflect policy limit in checkbox
368 mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED);
369 mChart.bindNetworkPolicy(policy);
370
371 // generate cycle list based on policy and available history
372 updateCycleList(policy);
373
374 // kick preference views so they rebind from changes above
375 refreshPreferenceViews();
376 }
377
378 /**
Jeff Sharkey8a503642011-06-10 13:31:21 -0700379 * Return full time bounds (earliest and latest time recorded) of the given
380 * {@link NetworkStatsHistory}.
381 */
382 private static long[] getHistoryBounds(NetworkStatsHistory history) {
383 final long currentTime = System.currentTimeMillis();
384
385 long start = currentTime;
386 long end = currentTime;
387 if (history.bucketCount > 0) {
388 start = history.bucketStart[0];
389 end = history.bucketStart[history.bucketCount - 1];
390 }
391
392 return new long[] { start, end };
393 }
394
395 /**
396 * Rebuild {@link #mCycleAdapter} based on {@link NetworkPolicy#cycleDay}
397 * and available {@link NetworkStatsHistory} data. Always selects the newest
398 * item, updating the inspection range on {@link #mChart}.
399 */
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700400 private void updateCycleList(NetworkPolicy policy) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700401 mCycleAdapter.clear();
402
403 final Context context = mCycleSpinner.getContext();
404
405 final long[] bounds = getHistoryBounds(mHistory);
406 final long historyStart = bounds[0];
407 final long historyEnd = bounds[1];
408
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700409 if (policy != null) {
410 // find the next cycle boundary
411 long cycleEnd = computeNextCycleBoundary(historyEnd, policy);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700412
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700413 int guardCount = 0;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700414
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700415 // walk backwards, generating all valid cycle ranges
416 while (cycleEnd > historyStart) {
417 final long cycleStart = computeLastCycleBoundary(cycleEnd, policy);
418 Log.d(TAG, "generating cs=" + cycleStart + " to ce=" + cycleEnd + " waiting for hs="
419 + historyStart);
420 mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd));
421 cycleEnd = cycleStart;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700422
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700423 // TODO: remove this guard once we have better testing
424 if (guardCount++ > 50) {
425 Log.wtf(TAG, "stuck generating ranges for bounds=" + Arrays.toString(bounds)
426 + " and policy=" + policy);
427 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700428 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700429
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700430 // one last cycle entry to modify policy cycle day
431 mCycleAdapter.add(new CycleChangeItem(context));
432
433 } else {
434 // no valid cycle; show all data
435 // TODO: offer simple ranges like "last week" etc
436 mCycleAdapter.add(new CycleItem(context, historyStart, historyEnd));
437
438 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700439
440 // force pick the current cycle (first item)
441 mCycleSpinner.setSelection(0);
442 mCycleListener.onItemSelected(mCycleSpinner, null, 0, 0);
443 }
444
445 /**
446 * Force rebind of hijacked {@link Preference} views.
447 */
448 private void refreshPreferenceViews() {
449 mDataEnabledView = mDataEnabled.getView(mDataEnabledView, mListView);
450 mDisableAtLimitView = mDisableAtLimit.getView(mDisableAtLimitView, mListView);
451 }
452
453 private OnClickListener mDataEnabledListener = new OnClickListener() {
454 /** {@inheritDoc} */
455 public void onClick(View v) {
456 mDataEnabled.setChecked(!mDataEnabled.isChecked());
457 refreshPreferenceViews();
458
459 // TODO: wire up to telephony to enable/disable radios
460 }
461 };
462
463 private OnClickListener mDisableAtLimitListener = new OnClickListener() {
464 /** {@inheritDoc} */
465 public void onClick(View v) {
466 final boolean disableAtLimit = !mDisableAtLimit.isChecked();
467 mDisableAtLimit.setChecked(disableAtLimit);
468 refreshPreferenceViews();
469
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700470 // TODO: create policy if none exists
Jeff Sharkey8a503642011-06-10 13:31:21 -0700471 // TODO: show interstitial warning dialog to user
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700472 final long limitBytes = disableAtLimit ? 5 * GB_IN_BYTES : LIMIT_DISABLED;
473 mPolicyModifier.setPolicyLimitBytes(mTemplate, limitBytes);
474 updatePolicy();
Jeff Sharkey8a503642011-06-10 13:31:21 -0700475 }
476 };
477
478 private OnItemClickListener mListListener = new OnItemClickListener() {
479 /** {@inheritDoc} */
480 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
481 final Object object = parent.getItemAtPosition(position);
482
483 // TODO: show app details
484 Log.d(TAG, "showing app details for " + object);
485 }
486 };
487
488 private OnItemSelectedListener mCycleListener = new OnItemSelectedListener() {
489 /** {@inheritDoc} */
490 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
491 final CycleItem cycle = (CycleItem) parent.getItemAtPosition(position);
492 if (cycle instanceof CycleChangeItem) {
493 // TODO: show "define cycle" dialog
494 // also reset back to first cycle
495 Log.d(TAG, "CHANGE CYCLE DIALOG!!");
496
497 } else {
498 if (LOGD) Log.d(TAG, "shoiwng cycle " + cycle);
499
500 // update chart to show selected cycle, and update detail data
501 // to match updated sweep bounds.
502 final long[] bounds = getHistoryBounds(mHistory);
503 mChart.setVisibleRange(cycle.start, cycle.end, bounds[1]);
504
505 updateDetailData();
506 }
507 }
508
509 /** {@inheritDoc} */
510 public void onNothingSelected(AdapterView<?> parent) {
511 // ignored
512 }
513 };
514
515 /**
516 * Update {@link #mAdapter} with sorted list of applications data usage,
517 * based on current inspection from {@link #mChart}.
518 */
519 private void updateDetailData() {
520 if (LOGD) Log.d(TAG, "updateDetailData()");
521
522 try {
523 final long[] range = mChart.getInspectRange();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700524 final NetworkStats stats = mStatsService.getSummaryForAllUid(
Jeff Sharkey8a503642011-06-10 13:31:21 -0700525 range[0], range[1], mTemplate);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700526 mAdapter.bindStats(stats);
527 } catch (RemoteException e) {
528 Log.w(TAG, "problem reading stats");
529 }
530 }
531
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700532 private static String getActiveSubscriberId(Context context) {
533 final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
534 Context.TELEPHONY_SERVICE);
535 return telephony.getSubscriberId();
536 }
537
Jeff Sharkey8a503642011-06-10 13:31:21 -0700538 private DataUsageChartListener mChartListener = new DataUsageChartListener() {
539 /** {@inheritDoc} */
540 public void onInspectRangeChanged() {
541 if (LOGD) Log.d(TAG, "onInspectRangeChanged()");
542 updateDetailData();
543 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700544
Jeff Sharkey8a503642011-06-10 13:31:21 -0700545 /** {@inheritDoc} */
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700546 public void onWarningChanged() {
547 if (LOGD) Log.d(TAG, "onWarningChanged()");
Jeff Sharkey8a503642011-06-10 13:31:21 -0700548 final long warningBytes = mChart.getWarningBytes();
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700549 mPolicyModifier.setPolicyWarningBytes(mTemplate, warningBytes);
550 updatePolicy();
551 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700552
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700553 /** {@inheritDoc} */
554 public void onLimitChanged() {
555 if (LOGD) Log.d(TAG, "onLimitChanged()");
556 final long limitBytes = mDisableAtLimit.isChecked() ? mChart.getLimitBytes()
557 : LIMIT_DISABLED;
558 mPolicyModifier.setPolicyLimitBytes(mTemplate, limitBytes);
559 updatePolicy();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700560 }
561 };
562
563
564 /**
Jeff Sharkey8a503642011-06-10 13:31:21 -0700565 * List item that reflects a specific data usage cycle.
566 */
567 public static class CycleItem {
568 public CharSequence label;
569 public long start;
570 public long end;
571
572 private static final StringBuilder sBuilder = new StringBuilder(50);
573 private static final java.util.Formatter sFormatter = new java.util.Formatter(
574 sBuilder, Locale.getDefault());
575
576 CycleItem(CharSequence label) {
577 this.label = label;
578 }
579
580 public CycleItem(Context context, long start, long end) {
581 this.label = formatDateRangeUtc(context, start, end);
582 this.start = start;
583 this.end = end;
584 }
585
586 private static String formatDateRangeUtc(Context context, long start, long end) {
587 synchronized (sBuilder) {
588 sBuilder.setLength(0);
589 return DateUtils.formatDateRange(context, sFormatter, start, end,
590 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH,
591 Time.TIMEZONE_UTC).toString();
592 }
593 }
594
595 @Override
596 public String toString() {
597 return label.toString();
598 }
599 }
600
601 /**
602 * Special-case data usage cycle that triggers dialog to change
603 * {@link NetworkPolicy#cycleDay}.
604 */
605 public static class CycleChangeItem extends CycleItem {
606 public CycleChangeItem(Context context) {
607 super(context.getString(R.string.data_usage_change_cycle));
608 }
609 }
610
611 public static class CycleAdapter extends ArrayAdapter<CycleItem> {
612 public CycleAdapter(Context context) {
613 super(context, android.R.layout.simple_spinner_item);
614 setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
615 }
616 }
617
618 /**
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700619 * Adapter of applications, sorted by total usage descending.
620 */
621 public static class DataUsageAdapter extends BaseAdapter {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700622 private ArrayList<AppUsageItem> mItems = Lists.newArrayList();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700623
Jeff Sharkey8a503642011-06-10 13:31:21 -0700624 private static class AppUsageItem implements Comparable<AppUsageItem> {
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700625 public int uid;
626 public long total;
627
628 /** {@inheritDoc} */
Jeff Sharkey8a503642011-06-10 13:31:21 -0700629 public int compareTo(AppUsageItem another) {
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700630 return Long.compare(another.total, total);
631 }
632 }
633
634 public void bindStats(NetworkStats stats) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700635 mItems.clear();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700636
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700637 for (int i = 0; i < stats.size; i++) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700638 final AppUsageItem item = new AppUsageItem();
639 item.uid = stats.uid[i];
640 item.total = stats.rx[i] + stats.tx[i];
641 mItems.add(item);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700642 }
643
Jeff Sharkey8a503642011-06-10 13:31:21 -0700644 Collections.sort(mItems);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700645 notifyDataSetChanged();
646 }
647
648 @Override
649 public int getCount() {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700650 return mItems.size();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700651 }
652
653 @Override
654 public Object getItem(int position) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700655 return mItems.get(position);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700656 }
657
658 @Override
659 public long getItemId(int position) {
660 return position;
661 }
662
663 @Override
664 public View getView(int position, View convertView, ViewGroup parent) {
665 if (convertView == null) {
666 convertView = LayoutInflater.from(parent.getContext()).inflate(
667 android.R.layout.simple_list_item_2, parent, false);
668 }
669
670 final Context context = parent.getContext();
671 final PackageManager pm = context.getPackageManager();
672
673 final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
674 final TextView text2 = (TextView) convertView.findViewById(android.R.id.text2);
675
Jeff Sharkey8a503642011-06-10 13:31:21 -0700676 final AppUsageItem item = mItems.get(position);
677 text1.setText(pm.getNameForUid(item.uid));
678 text2.setText(Formatter.formatFileSize(context, item.total));
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700679
680 return convertView;
681 }
682
683 }
684
685
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700686}