blob: 19caed87861979152c9bd478e00a80b2a46039a3 [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;
Jeff Sharkey4dfa6602011-06-13 00:42:03 -070030import android.content.Intent;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070031import android.content.pm.PackageManager;
Jeff Sharkey8a503642011-06-10 13:31:21 -070032import android.net.INetworkPolicyManager;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070033import android.net.INetworkStatsService;
Jeff Sharkey8a503642011-06-10 13:31:21 -070034import android.net.NetworkPolicy;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070035import android.net.NetworkStats;
36import android.net.NetworkStatsHistory;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070037import android.os.Bundle;
38import android.os.RemoteException;
39import android.os.ServiceManager;
Jeff Sharkey8a503642011-06-10 13:31:21 -070040import android.preference.CheckBoxPreference;
41import android.preference.Preference;
Jeff Sharkey4dfa6602011-06-13 00:42:03 -070042import android.preference.PreferenceActivity;
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -070043import android.preference.SwitchPreference;
44import android.telephony.TelephonyManager;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070045import android.text.format.DateUtils;
46import android.text.format.Formatter;
Jeff Sharkey8a503642011-06-10 13:31:21 -070047import android.text.format.Time;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070048import android.util.Log;
49import android.view.LayoutInflater;
Jeff Sharkey8a503642011-06-10 13:31:21 -070050import android.view.Menu;
51import android.view.MenuInflater;
52import android.view.MenuItem;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070053import android.view.View;
Jeff Sharkey8a503642011-06-10 13:31:21 -070054import android.view.View.OnClickListener;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070055import android.view.ViewGroup;
Jeff Sharkey8a503642011-06-10 13:31:21 -070056import android.widget.AbsListView;
57import android.widget.AdapterView;
58import android.widget.AdapterView.OnItemClickListener;
59import android.widget.AdapterView.OnItemSelectedListener;
60import android.widget.ArrayAdapter;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070061import android.widget.BaseAdapter;
Jeff Sharkey8a503642011-06-10 13:31:21 -070062import android.widget.LinearLayout;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070063import android.widget.ListView;
Jeff Sharkey8a503642011-06-10 13:31:21 -070064import android.widget.Spinner;
65import android.widget.TabHost;
66import android.widget.TabHost.OnTabChangeListener;
67import android.widget.TabHost.TabContentFactory;
68import android.widget.TabHost.TabSpec;
69import android.widget.TabWidget;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070070import android.widget.TextView;
71
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -070072import com.android.settings.net.NetworkPolicyModifier;
Jeff Sharkey8a503642011-06-10 13:31:21 -070073import com.android.settings.widget.DataUsageChartView;
74import com.android.settings.widget.DataUsageChartView.DataUsageChartListener;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070075import com.google.android.collect.Lists;
76
77import java.util.ArrayList;
Jeff Sharkey8a503642011-06-10 13:31:21 -070078import java.util.Arrays;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070079import java.util.Collections;
Jeff Sharkey8a503642011-06-10 13:31:21 -070080import java.util.Locale;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070081
82public class DataUsageSummary extends Fragment {
83 private static final String TAG = "DataUsage";
Jeff Sharkey8a503642011-06-10 13:31:21 -070084 private static final boolean LOGD = true;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070085
Jeff Sharkey8a503642011-06-10 13:31:21 -070086 private static final int TEMPLATE_INVALID = -1;
87
88 private static final String TAB_3G = "3g";
89 private static final String TAB_4G = "4g";
90 private static final String TAB_MOBILE = "mobile";
91 private static final String TAB_WIFI = "wifi";
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070092
93 private static final long KB_IN_BYTES = 1024;
94 private static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
95 private static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
96
97 private INetworkStatsService mStatsService;
Jeff Sharkey8a503642011-06-10 13:31:21 -070098 private INetworkPolicyManager mPolicyService;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070099
Jeff Sharkey8a503642011-06-10 13:31:21 -0700100 private TabHost mTabHost;
101 private TabWidget mTabWidget;
102 private ListView mListView;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700103 private DataUsageAdapter mAdapter;
104
Jeff Sharkey8a503642011-06-10 13:31:21 -0700105 private View mHeader;
106 private LinearLayout mSwitches;
107
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700108 private SwitchPreference mDataEnabled;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700109 private CheckBoxPreference mDisableAtLimit;
110 private View mDataEnabledView;
111 private View mDisableAtLimitView;
112
113 private DataUsageChartView mChart;
114
115 private Spinner mCycleSpinner;
116 private CycleAdapter mCycleAdapter;
117
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700118 // TODO: persist show wifi flag
Jeff Sharkey8a503642011-06-10 13:31:21 -0700119 private boolean mShowWifi = false;
120
121 private int mTemplate = TEMPLATE_INVALID;
122
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700123 private NetworkPolicyModifier mPolicyModifier;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700124 private NetworkStatsHistory mHistory;
125
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700126 @Override
Jeff Sharkey8a503642011-06-10 13:31:21 -0700127 public void onCreate(Bundle savedInstanceState) {
128 super.onCreate(savedInstanceState);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700129
130 mStatsService = INetworkStatsService.Stub.asInterface(
131 ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
Jeff Sharkey8a503642011-06-10 13:31:21 -0700132 mPolicyService = INetworkPolicyManager.Stub.asInterface(
133 ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700134
135 final Context context = getActivity();
136 final String subscriberId = getActiveSubscriberId(context);
137 mPolicyModifier = new NetworkPolicyModifier(mPolicyService, subscriberId);
Jeff Sharkey94a90952011-06-13 22:31:09 -0700138 mPolicyModifier.read();
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700139
140 setHasOptionsMenu(true);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700141 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700142
Jeff Sharkey8a503642011-06-10 13:31:21 -0700143 @Override
144 public View onCreateView(LayoutInflater inflater, ViewGroup container,
145 Bundle savedInstanceState) {
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700146
Jeff Sharkey8a503642011-06-10 13:31:21 -0700147 final Context context = inflater.getContext();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700148 final View view = inflater.inflate(R.layout.data_usage_summary, container, false);
149
Jeff Sharkey8a503642011-06-10 13:31:21 -0700150 mTabHost = (TabHost) view.findViewById(android.R.id.tabhost);
151 mTabWidget = (TabWidget) view.findViewById(android.R.id.tabs);
152 mListView = (ListView) view.findViewById(android.R.id.list);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700153
Jeff Sharkey8a503642011-06-10 13:31:21 -0700154 mTabHost.setup();
155 mTabHost.setOnTabChangedListener(mTabListener);
156
157 mHeader = inflater.inflate(R.layout.data_usage_header, mListView, false);
158 mListView.addHeaderView(mHeader, null, false);
159
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700160 mDataEnabled = new SwitchPreference(context);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700161 mDisableAtLimit = new CheckBoxPreference(context);
162
163 // kick refresh once to force-create views
164 refreshPreferenceViews();
165
166 // TODO: remove once thin preferences are supported (48dip)
167 mDataEnabledView.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, 72));
168 mDisableAtLimitView.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, 72));
169
170 mDataEnabledView.setOnClickListener(mDataEnabledListener);
171 mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener);
172
173 mSwitches = (LinearLayout) mHeader.findViewById(R.id.switches);
174 mSwitches.addView(mDataEnabledView);
175 mSwitches.addView(mDisableAtLimitView);
176
177 mCycleSpinner = (Spinner) mHeader.findViewById(R.id.cycles);
178 mCycleAdapter = new CycleAdapter(context);
179 mCycleSpinner.setAdapter(mCycleAdapter);
180 mCycleSpinner.setOnItemSelectedListener(mCycleListener);
181
182 mChart = new DataUsageChartView(context);
183 mChart.setListener(mChartListener);
184 mChart.setLayoutParams(new AbsListView.LayoutParams(MATCH_PARENT, 350));
185 mListView.addHeaderView(mChart, null, false);
186
187 mAdapter = new DataUsageAdapter();
188 mListView.setOnItemClickListener(mListListener);
189 mListView.setAdapter(mAdapter);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700190
191 return view;
192 }
193
194 @Override
195 public void onResume() {
196 super.onResume();
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
Jeff Sharkey94a90952011-06-13 22:31:09 -0700234 @Override
235 public void onDestroyView() {
236 super.onDestroyView();
237
238 mDataEnabledView = null;
239 mDisableAtLimitView = null;
240 }
241
Jeff Sharkey8a503642011-06-10 13:31:21 -0700242 /**
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700243 * Rebuild all tabs based on {@link NetworkPolicyModifier} and
244 * {@link #mShowWifi}, hiding the tabs entirely when applicable. Selects
245 * first tab, and kicks off a full rebind of body contents.
Jeff Sharkey8a503642011-06-10 13:31:21 -0700246 */
247 private void updateTabs() {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700248 final boolean mobileSplit = mPolicyModifier.isMobilePolicySplit();
249 final boolean tabsVisible = mobileSplit || mShowWifi;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700250 mTabWidget.setVisibility(tabsVisible ? View.VISIBLE : View.GONE);
251 mTabHost.clearAllTabs();
252
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700253 if (mobileSplit) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700254 mTabHost.addTab(buildTabSpec(TAB_3G, R.string.data_usage_tab_3g));
255 mTabHost.addTab(buildTabSpec(TAB_4G, R.string.data_usage_tab_4g));
256 }
257
258 if (mShowWifi) {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700259 if (!mobileSplit) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700260 mTabHost.addTab(buildTabSpec(TAB_MOBILE, R.string.data_usage_tab_mobile));
261 }
262 mTabHost.addTab(buildTabSpec(TAB_WIFI, R.string.data_usage_tab_wifi));
263 }
264
265 if (mTabWidget.getTabCount() > 0) {
266 // select first tab, which will kick off updateBody()
267 mTabHost.setCurrentTab(0);
268 } else {
269 // no tabs shown; update body manually
270 updateBody();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700271 }
272 }
273
Jeff Sharkey8a503642011-06-10 13:31:21 -0700274 /**
275 * Factory that provide empty {@link View} to make {@link TabHost} happy.
276 */
277 private TabContentFactory mEmptyTabContent = new TabContentFactory() {
278 /** {@inheritDoc} */
279 public View createTabContent(String tag) {
280 return new View(mTabHost.getContext());
281 }
282 };
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700283
Jeff Sharkey8a503642011-06-10 13:31:21 -0700284 /**
285 * Build {@link TabSpec} with thin indicator, and empty content.
286 */
287 private TabSpec buildTabSpec(String tag, int titleRes) {
288 final LayoutInflater inflater = LayoutInflater.from(mTabWidget.getContext());
289 final View indicator = inflater.inflate(
290 R.layout.tab_indicator_thin_holo, mTabWidget, false);
291 final TextView title = (TextView) indicator.findViewById(android.R.id.title);
292 title.setText(titleRes);
293 return mTabHost.newTabSpec(tag).setIndicator(indicator).setContent(mEmptyTabContent);
294 }
295
296 private OnTabChangeListener mTabListener = new OnTabChangeListener() {
297 /** {@inheritDoc} */
298 public void onTabChanged(String tabId) {
299 // user changed tab; update body
300 updateBody();
301 }
302 };
303
304 /**
305 * Update body content based on current tab. Loads
306 * {@link NetworkStatsHistory} and {@link NetworkPolicy} from system, and
307 * binds them to visible controls.
308 */
309 private void updateBody() {
310 final String tabTag = mTabHost.getCurrentTabTag();
311 final String currentTab = tabTag != null ? tabTag : TAB_MOBILE;
312
313 if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab);
314
315 if (TAB_WIFI.equals(currentTab)) {
316 // wifi doesn't have any controls
317 mDataEnabledView.setVisibility(View.GONE);
318 mDisableAtLimitView.setVisibility(View.GONE);
319 mTemplate = TEMPLATE_WIFI;
320
321 } else {
322 // make sure we show for non-wifi
323 mDataEnabledView.setVisibility(View.VISIBLE);
324 mDisableAtLimitView.setVisibility(View.VISIBLE);
325 }
326
327 if (TAB_MOBILE.equals(currentTab)) {
328 mDataEnabled.setTitle(R.string.data_usage_enable_mobile);
329 mDisableAtLimit.setTitle(R.string.data_usage_disable_mobile_limit);
330 mTemplate = TEMPLATE_MOBILE_ALL;
331
332 } else if (TAB_3G.equals(currentTab)) {
333 mDataEnabled.setTitle(R.string.data_usage_enable_3g);
334 mDisableAtLimit.setTitle(R.string.data_usage_disable_3g_limit);
335 mTemplate = TEMPLATE_MOBILE_3G_LOWER;
336
337 } else if (TAB_4G.equals(currentTab)) {
338 mDataEnabled.setTitle(R.string.data_usage_enable_4g);
339 mDisableAtLimit.setTitle(R.string.data_usage_disable_4g_limit);
340 mTemplate = TEMPLATE_MOBILE_4G;
341
342 }
343
344 // TODO: populate checkbox based on radio preferences
345 mDataEnabled.setChecked(true);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700346
347 try {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700348 // load stats for current template
Jeff Sharkey8a503642011-06-10 13:31:21 -0700349 mHistory = mStatsService.getHistoryForNetwork(mTemplate);
350 } catch (RemoteException e) {
351 // since we can't do much without policy or history, and we don't
352 // want to leave with half-baked UI, we bail hard.
353 throw new RuntimeException("problem reading network policy or stats", e);
354 }
355
Jeff Sharkey8a503642011-06-10 13:31:21 -0700356 // bind chart to historical stats
Jeff Sharkey8a503642011-06-10 13:31:21 -0700357 mChart.bindNetworkStats(mHistory);
358
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700359 updatePolicy(true);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700360
361 // force scroll to top of body
362 mListView.smoothScrollToPosition(0);
363
364 // kick preference views so they rebind from changes above
365 refreshPreferenceViews();
366 }
367
368 /**
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700369 * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for
370 * current {@link #mTemplate}.
371 */
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700372 private void updatePolicy(boolean refreshCycle) {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700373 final NetworkPolicy policy = mPolicyModifier.getPolicy(mTemplate);
374
375 // reflect policy limit in checkbox
376 mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED);
377 mChart.bindNetworkPolicy(policy);
378
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700379 if (refreshCycle) {
380 // generate cycle list based on policy and available history
381 updateCycleList(policy);
382 }
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700383
384 // kick preference views so they rebind from changes above
385 refreshPreferenceViews();
386 }
387
388 /**
Jeff Sharkey8a503642011-06-10 13:31:21 -0700389 * Return full time bounds (earliest and latest time recorded) of the given
390 * {@link NetworkStatsHistory}.
391 */
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700392 public static long[] getHistoryBounds(NetworkStatsHistory history) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700393 final long currentTime = System.currentTimeMillis();
394
395 long start = currentTime;
396 long end = currentTime;
397 if (history.bucketCount > 0) {
398 start = history.bucketStart[0];
399 end = history.bucketStart[history.bucketCount - 1];
400 }
401
402 return new long[] { start, end };
403 }
404
405 /**
406 * Rebuild {@link #mCycleAdapter} based on {@link NetworkPolicy#cycleDay}
407 * and available {@link NetworkStatsHistory} data. Always selects the newest
408 * item, updating the inspection range on {@link #mChart}.
409 */
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700410 private void updateCycleList(NetworkPolicy policy) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700411 mCycleAdapter.clear();
412
413 final Context context = mCycleSpinner.getContext();
414
415 final long[] bounds = getHistoryBounds(mHistory);
416 final long historyStart = bounds[0];
417 final long historyEnd = bounds[1];
418
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700419 if (policy != null) {
420 // find the next cycle boundary
421 long cycleEnd = computeNextCycleBoundary(historyEnd, policy);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700422
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700423 int guardCount = 0;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700424
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700425 // walk backwards, generating all valid cycle ranges
426 while (cycleEnd > historyStart) {
427 final long cycleStart = computeLastCycleBoundary(cycleEnd, policy);
428 Log.d(TAG, "generating cs=" + cycleStart + " to ce=" + cycleEnd + " waiting for hs="
429 + historyStart);
430 mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd));
431 cycleEnd = cycleStart;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700432
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700433 // TODO: remove this guard once we have better testing
434 if (guardCount++ > 50) {
435 Log.wtf(TAG, "stuck generating ranges for bounds=" + Arrays.toString(bounds)
436 + " and policy=" + policy);
437 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700438 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700439
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700440 // one last cycle entry to modify policy cycle day
441 mCycleAdapter.add(new CycleChangeItem(context));
442
443 } else {
444 // no valid cycle; show all data
445 // TODO: offer simple ranges like "last week" etc
446 mCycleAdapter.add(new CycleItem(context, historyStart, historyEnd));
447
448 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700449
450 // force pick the current cycle (first item)
451 mCycleSpinner.setSelection(0);
452 mCycleListener.onItemSelected(mCycleSpinner, null, 0, 0);
453 }
454
455 /**
456 * Force rebind of hijacked {@link Preference} views.
457 */
458 private void refreshPreferenceViews() {
459 mDataEnabledView = mDataEnabled.getView(mDataEnabledView, mListView);
460 mDisableAtLimitView = mDisableAtLimit.getView(mDisableAtLimitView, mListView);
461 }
462
463 private OnClickListener mDataEnabledListener = new OnClickListener() {
464 /** {@inheritDoc} */
465 public void onClick(View v) {
466 mDataEnabled.setChecked(!mDataEnabled.isChecked());
467 refreshPreferenceViews();
468
469 // TODO: wire up to telephony to enable/disable radios
470 }
471 };
472
473 private OnClickListener mDisableAtLimitListener = new OnClickListener() {
474 /** {@inheritDoc} */
475 public void onClick(View v) {
476 final boolean disableAtLimit = !mDisableAtLimit.isChecked();
477 mDisableAtLimit.setChecked(disableAtLimit);
478 refreshPreferenceViews();
479
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700480 // TODO: create policy if none exists
Jeff Sharkey8a503642011-06-10 13:31:21 -0700481 // TODO: show interstitial warning dialog to user
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700482 final long limitBytes = disableAtLimit ? 5 * GB_IN_BYTES : LIMIT_DISABLED;
483 mPolicyModifier.setPolicyLimitBytes(mTemplate, limitBytes);
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700484 updatePolicy(false);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700485 }
486 };
487
488 private OnItemClickListener mListListener = new OnItemClickListener() {
489 /** {@inheritDoc} */
490 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700491 final AppUsageItem app = (AppUsageItem) parent.getItemAtPosition(position);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700492
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700493 final Bundle args = new Bundle();
494 args.putInt(Intent.EXTRA_UID, app.uid);
495
496 final PreferenceActivity activity = (PreferenceActivity) getActivity();
497 activity.startPreferencePanel(DataUsageAppDetail.class.getName(), args,
498 R.string.data_usage_summary_title, null, null, 0);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700499 }
500 };
501
502 private OnItemSelectedListener mCycleListener = new OnItemSelectedListener() {
503 /** {@inheritDoc} */
504 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
505 final CycleItem cycle = (CycleItem) parent.getItemAtPosition(position);
506 if (cycle instanceof CycleChangeItem) {
507 // TODO: show "define cycle" dialog
508 // also reset back to first cycle
509 Log.d(TAG, "CHANGE CYCLE DIALOG!!");
510
511 } else {
512 if (LOGD) Log.d(TAG, "shoiwng cycle " + cycle);
513
514 // update chart to show selected cycle, and update detail data
515 // to match updated sweep bounds.
516 final long[] bounds = getHistoryBounds(mHistory);
517 mChart.setVisibleRange(cycle.start, cycle.end, bounds[1]);
518
519 updateDetailData();
520 }
521 }
522
523 /** {@inheritDoc} */
524 public void onNothingSelected(AdapterView<?> parent) {
525 // ignored
526 }
527 };
528
529 /**
530 * Update {@link #mAdapter} with sorted list of applications data usage,
531 * based on current inspection from {@link #mChart}.
532 */
533 private void updateDetailData() {
534 if (LOGD) Log.d(TAG, "updateDetailData()");
535
536 try {
537 final long[] range = mChart.getInspectRange();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700538 final NetworkStats stats = mStatsService.getSummaryForAllUid(
Jeff Sharkey8a503642011-06-10 13:31:21 -0700539 range[0], range[1], mTemplate);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700540 mAdapter.bindStats(stats);
541 } catch (RemoteException e) {
542 Log.w(TAG, "problem reading stats");
543 }
544 }
545
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700546 private static String getActiveSubscriberId(Context context) {
547 final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
548 Context.TELEPHONY_SERVICE);
549 return telephony.getSubscriberId();
550 }
551
Jeff Sharkey8a503642011-06-10 13:31:21 -0700552 private DataUsageChartListener mChartListener = new DataUsageChartListener() {
553 /** {@inheritDoc} */
554 public void onInspectRangeChanged() {
555 if (LOGD) Log.d(TAG, "onInspectRangeChanged()");
556 updateDetailData();
557 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700558
Jeff Sharkey8a503642011-06-10 13:31:21 -0700559 /** {@inheritDoc} */
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700560 public void onWarningChanged() {
561 if (LOGD) Log.d(TAG, "onWarningChanged()");
Jeff Sharkey8a503642011-06-10 13:31:21 -0700562 final long warningBytes = mChart.getWarningBytes();
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700563 mPolicyModifier.setPolicyWarningBytes(mTemplate, warningBytes);
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700564 updatePolicy(false);
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700565 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700566
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700567 /** {@inheritDoc} */
568 public void onLimitChanged() {
569 if (LOGD) Log.d(TAG, "onLimitChanged()");
570 final long limitBytes = mDisableAtLimit.isChecked() ? mChart.getLimitBytes()
571 : LIMIT_DISABLED;
572 mPolicyModifier.setPolicyLimitBytes(mTemplate, limitBytes);
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700573 updatePolicy(false);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700574 }
575 };
576
577
578 /**
Jeff Sharkey8a503642011-06-10 13:31:21 -0700579 * List item that reflects a specific data usage cycle.
580 */
581 public static class CycleItem {
582 public CharSequence label;
583 public long start;
584 public long end;
585
586 private static final StringBuilder sBuilder = new StringBuilder(50);
587 private static final java.util.Formatter sFormatter = new java.util.Formatter(
588 sBuilder, Locale.getDefault());
589
590 CycleItem(CharSequence label) {
591 this.label = label;
592 }
593
594 public CycleItem(Context context, long start, long end) {
595 this.label = formatDateRangeUtc(context, start, end);
596 this.start = start;
597 this.end = end;
598 }
599
600 private static String formatDateRangeUtc(Context context, long start, long end) {
601 synchronized (sBuilder) {
602 sBuilder.setLength(0);
603 return DateUtils.formatDateRange(context, sFormatter, start, end,
604 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH,
605 Time.TIMEZONE_UTC).toString();
606 }
607 }
608
609 @Override
610 public String toString() {
611 return label.toString();
612 }
613 }
614
615 /**
616 * Special-case data usage cycle that triggers dialog to change
617 * {@link NetworkPolicy#cycleDay}.
618 */
619 public static class CycleChangeItem extends CycleItem {
620 public CycleChangeItem(Context context) {
621 super(context.getString(R.string.data_usage_change_cycle));
622 }
623 }
624
625 public static class CycleAdapter extends ArrayAdapter<CycleItem> {
626 public CycleAdapter(Context context) {
627 super(context, android.R.layout.simple_spinner_item);
628 setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
629 }
630 }
631
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700632 private static class AppUsageItem implements Comparable<AppUsageItem> {
633 public int uid;
634 public long total;
635
636 /** {@inheritDoc} */
637 public int compareTo(AppUsageItem another) {
638 return Long.compare(another.total, total);
639 }
640 }
641
Jeff Sharkey8a503642011-06-10 13:31:21 -0700642 /**
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700643 * Adapter of applications, sorted by total usage descending.
644 */
645 public static class DataUsageAdapter extends BaseAdapter {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700646 private ArrayList<AppUsageItem> mItems = Lists.newArrayList();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700647
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700648 public void bindStats(NetworkStats stats) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700649 mItems.clear();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700650
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700651 for (int i = 0; i < stats.size; i++) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700652 final AppUsageItem item = new AppUsageItem();
653 item.uid = stats.uid[i];
654 item.total = stats.rx[i] + stats.tx[i];
655 mItems.add(item);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700656 }
657
Jeff Sharkey8a503642011-06-10 13:31:21 -0700658 Collections.sort(mItems);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700659 notifyDataSetChanged();
660 }
661
662 @Override
663 public int getCount() {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700664 return mItems.size();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700665 }
666
667 @Override
668 public Object getItem(int position) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700669 return mItems.get(position);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700670 }
671
672 @Override
673 public long getItemId(int position) {
674 return position;
675 }
676
677 @Override
678 public View getView(int position, View convertView, ViewGroup parent) {
679 if (convertView == null) {
680 convertView = LayoutInflater.from(parent.getContext()).inflate(
681 android.R.layout.simple_list_item_2, parent, false);
682 }
683
684 final Context context = parent.getContext();
685 final PackageManager pm = context.getPackageManager();
686
687 final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
688 final TextView text2 = (TextView) convertView.findViewById(android.R.id.text2);
689
Jeff Sharkey8a503642011-06-10 13:31:21 -0700690 final AppUsageItem item = mItems.get(position);
691 text1.setText(pm.getNameForUid(item.uid));
692 text2.setText(Formatter.formatFileSize(context, item.total));
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700693
694 return convertView;
695 }
696
697 }
698
699
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700700}