blob: 2350d540dbacc222372c594acafcb2a68a066cf0 [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);
138
139 setHasOptionsMenu(true);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700140 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700141
Jeff Sharkey8a503642011-06-10 13:31:21 -0700142 @Override
143 public View onCreateView(LayoutInflater inflater, ViewGroup container,
144 Bundle savedInstanceState) {
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700145
Jeff Sharkey8a503642011-06-10 13:31:21 -0700146 final Context context = inflater.getContext();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700147 final View view = inflater.inflate(R.layout.data_usage_summary, container, false);
148
Jeff Sharkey8a503642011-06-10 13:31:21 -0700149 mTabHost = (TabHost) view.findViewById(android.R.id.tabhost);
150 mTabWidget = (TabWidget) view.findViewById(android.R.id.tabs);
151 mListView = (ListView) view.findViewById(android.R.id.list);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700152
Jeff Sharkey8a503642011-06-10 13:31:21 -0700153 mTabHost.setup();
154 mTabHost.setOnTabChangedListener(mTabListener);
155
156 mHeader = inflater.inflate(R.layout.data_usage_header, mListView, false);
157 mListView.addHeaderView(mHeader, null, false);
158
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700159 mDataEnabled = new SwitchPreference(context);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700160 mDisableAtLimit = new CheckBoxPreference(context);
161
162 // kick refresh once to force-create views
163 refreshPreferenceViews();
164
165 // TODO: remove once thin preferences are supported (48dip)
166 mDataEnabledView.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, 72));
167 mDisableAtLimitView.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, 72));
168
169 mDataEnabledView.setOnClickListener(mDataEnabledListener);
170 mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener);
171
172 mSwitches = (LinearLayout) mHeader.findViewById(R.id.switches);
173 mSwitches.addView(mDataEnabledView);
174 mSwitches.addView(mDisableAtLimitView);
175
176 mCycleSpinner = (Spinner) mHeader.findViewById(R.id.cycles);
177 mCycleAdapter = new CycleAdapter(context);
178 mCycleSpinner.setAdapter(mCycleAdapter);
179 mCycleSpinner.setOnItemSelectedListener(mCycleListener);
180
181 mChart = new DataUsageChartView(context);
182 mChart.setListener(mChartListener);
183 mChart.setLayoutParams(new AbsListView.LayoutParams(MATCH_PARENT, 350));
184 mListView.addHeaderView(mChart, null, false);
185
186 mAdapter = new DataUsageAdapter();
187 mListView.setOnItemClickListener(mListListener);
188 mListView.setAdapter(mAdapter);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700189
190 return view;
191 }
192
193 @Override
194 public void onResume() {
195 super.onResume();
196
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700197 // read current policy state from service
198 mPolicyModifier.read();
199
Jeff Sharkey8a503642011-06-10 13:31:21 -0700200 // this kicks off chain reaction which creates tabs, binds the body to
201 // selected network, and binds chart, cycles and detail list.
202 updateTabs();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700203 }
204
Jeff Sharkey8a503642011-06-10 13:31:21 -0700205 @Override
206 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
207 inflater.inflate(R.menu.data_usage, menu);
208 }
209
210 @Override
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700211 public void onPrepareOptionsMenu(Menu menu) {
212 final MenuItem split4g = menu.findItem(R.id.action_split_4g);
213 split4g.setChecked(mPolicyModifier.isMobilePolicySplit());
214 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700215
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700216 @Override
217 public boolean onOptionsItemSelected(MenuItem item) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700218 switch (item.getItemId()) {
219 case R.id.action_split_4g: {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700220 final boolean mobileSplit = !item.isChecked();
221 mPolicyModifier.setMobilePolicySplit(mobileSplit);
222 item.setChecked(mPolicyModifier.isMobilePolicySplit());
Jeff Sharkey8a503642011-06-10 13:31:21 -0700223 updateTabs();
224 return true;
225 }
226 case R.id.action_show_wifi: {
227 mShowWifi = !item.isChecked();
228 item.setChecked(mShowWifi);
229 updateTabs();
230 return true;
231 }
232 }
233 return false;
234 }
235
236 /**
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700237 * Rebuild all tabs based on {@link NetworkPolicyModifier} and
238 * {@link #mShowWifi}, hiding the tabs entirely when applicable. Selects
239 * first tab, and kicks off a full rebind of body contents.
Jeff Sharkey8a503642011-06-10 13:31:21 -0700240 */
241 private void updateTabs() {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700242 final boolean mobileSplit = mPolicyModifier.isMobilePolicySplit();
243 final boolean tabsVisible = mobileSplit || mShowWifi;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700244 mTabWidget.setVisibility(tabsVisible ? View.VISIBLE : View.GONE);
245 mTabHost.clearAllTabs();
246
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700247 if (mobileSplit) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700248 mTabHost.addTab(buildTabSpec(TAB_3G, R.string.data_usage_tab_3g));
249 mTabHost.addTab(buildTabSpec(TAB_4G, R.string.data_usage_tab_4g));
250 }
251
252 if (mShowWifi) {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700253 if (!mobileSplit) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700254 mTabHost.addTab(buildTabSpec(TAB_MOBILE, R.string.data_usage_tab_mobile));
255 }
256 mTabHost.addTab(buildTabSpec(TAB_WIFI, R.string.data_usage_tab_wifi));
257 }
258
259 if (mTabWidget.getTabCount() > 0) {
260 // select first tab, which will kick off updateBody()
261 mTabHost.setCurrentTab(0);
262 } else {
263 // no tabs shown; update body manually
264 updateBody();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700265 }
266 }
267
Jeff Sharkey8a503642011-06-10 13:31:21 -0700268 /**
269 * Factory that provide empty {@link View} to make {@link TabHost} happy.
270 */
271 private TabContentFactory mEmptyTabContent = new TabContentFactory() {
272 /** {@inheritDoc} */
273 public View createTabContent(String tag) {
274 return new View(mTabHost.getContext());
275 }
276 };
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700277
Jeff Sharkey8a503642011-06-10 13:31:21 -0700278 /**
279 * Build {@link TabSpec} with thin indicator, and empty content.
280 */
281 private TabSpec buildTabSpec(String tag, int titleRes) {
282 final LayoutInflater inflater = LayoutInflater.from(mTabWidget.getContext());
283 final View indicator = inflater.inflate(
284 R.layout.tab_indicator_thin_holo, mTabWidget, false);
285 final TextView title = (TextView) indicator.findViewById(android.R.id.title);
286 title.setText(titleRes);
287 return mTabHost.newTabSpec(tag).setIndicator(indicator).setContent(mEmptyTabContent);
288 }
289
290 private OnTabChangeListener mTabListener = new OnTabChangeListener() {
291 /** {@inheritDoc} */
292 public void onTabChanged(String tabId) {
293 // user changed tab; update body
294 updateBody();
295 }
296 };
297
298 /**
299 * Update body content based on current tab. Loads
300 * {@link NetworkStatsHistory} and {@link NetworkPolicy} from system, and
301 * binds them to visible controls.
302 */
303 private void updateBody() {
304 final String tabTag = mTabHost.getCurrentTabTag();
305 final String currentTab = tabTag != null ? tabTag : TAB_MOBILE;
306
307 if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab);
308
309 if (TAB_WIFI.equals(currentTab)) {
310 // wifi doesn't have any controls
311 mDataEnabledView.setVisibility(View.GONE);
312 mDisableAtLimitView.setVisibility(View.GONE);
313 mTemplate = TEMPLATE_WIFI;
314
315 } else {
316 // make sure we show for non-wifi
317 mDataEnabledView.setVisibility(View.VISIBLE);
318 mDisableAtLimitView.setVisibility(View.VISIBLE);
319 }
320
321 if (TAB_MOBILE.equals(currentTab)) {
322 mDataEnabled.setTitle(R.string.data_usage_enable_mobile);
323 mDisableAtLimit.setTitle(R.string.data_usage_disable_mobile_limit);
324 mTemplate = TEMPLATE_MOBILE_ALL;
325
326 } else if (TAB_3G.equals(currentTab)) {
327 mDataEnabled.setTitle(R.string.data_usage_enable_3g);
328 mDisableAtLimit.setTitle(R.string.data_usage_disable_3g_limit);
329 mTemplate = TEMPLATE_MOBILE_3G_LOWER;
330
331 } else if (TAB_4G.equals(currentTab)) {
332 mDataEnabled.setTitle(R.string.data_usage_enable_4g);
333 mDisableAtLimit.setTitle(R.string.data_usage_disable_4g_limit);
334 mTemplate = TEMPLATE_MOBILE_4G;
335
336 }
337
338 // TODO: populate checkbox based on radio preferences
339 mDataEnabled.setChecked(true);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700340
341 try {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700342 // load stats for current template
Jeff Sharkey8a503642011-06-10 13:31:21 -0700343 mHistory = mStatsService.getHistoryForNetwork(mTemplate);
344 } catch (RemoteException e) {
345 // since we can't do much without policy or history, and we don't
346 // want to leave with half-baked UI, we bail hard.
347 throw new RuntimeException("problem reading network policy or stats", e);
348 }
349
Jeff Sharkey8a503642011-06-10 13:31:21 -0700350 // bind chart to historical stats
Jeff Sharkey8a503642011-06-10 13:31:21 -0700351 mChart.bindNetworkStats(mHistory);
352
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700353 updatePolicy(true);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700354
355 // force scroll to top of body
356 mListView.smoothScrollToPosition(0);
357
358 // kick preference views so they rebind from changes above
359 refreshPreferenceViews();
360 }
361
362 /**
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700363 * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for
364 * current {@link #mTemplate}.
365 */
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700366 private void updatePolicy(boolean refreshCycle) {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700367 final NetworkPolicy policy = mPolicyModifier.getPolicy(mTemplate);
368
369 // reflect policy limit in checkbox
370 mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED);
371 mChart.bindNetworkPolicy(policy);
372
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700373 if (refreshCycle) {
374 // generate cycle list based on policy and available history
375 updateCycleList(policy);
376 }
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700377
378 // kick preference views so they rebind from changes above
379 refreshPreferenceViews();
380 }
381
382 /**
Jeff Sharkey8a503642011-06-10 13:31:21 -0700383 * Return full time bounds (earliest and latest time recorded) of the given
384 * {@link NetworkStatsHistory}.
385 */
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700386 public static long[] getHistoryBounds(NetworkStatsHistory history) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700387 final long currentTime = System.currentTimeMillis();
388
389 long start = currentTime;
390 long end = currentTime;
391 if (history.bucketCount > 0) {
392 start = history.bucketStart[0];
393 end = history.bucketStart[history.bucketCount - 1];
394 }
395
396 return new long[] { start, end };
397 }
398
399 /**
400 * Rebuild {@link #mCycleAdapter} based on {@link NetworkPolicy#cycleDay}
401 * and available {@link NetworkStatsHistory} data. Always selects the newest
402 * item, updating the inspection range on {@link #mChart}.
403 */
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700404 private void updateCycleList(NetworkPolicy policy) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700405 mCycleAdapter.clear();
406
407 final Context context = mCycleSpinner.getContext();
408
409 final long[] bounds = getHistoryBounds(mHistory);
410 final long historyStart = bounds[0];
411 final long historyEnd = bounds[1];
412
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700413 if (policy != null) {
414 // find the next cycle boundary
415 long cycleEnd = computeNextCycleBoundary(historyEnd, policy);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700416
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700417 int guardCount = 0;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700418
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700419 // walk backwards, generating all valid cycle ranges
420 while (cycleEnd > historyStart) {
421 final long cycleStart = computeLastCycleBoundary(cycleEnd, policy);
422 Log.d(TAG, "generating cs=" + cycleStart + " to ce=" + cycleEnd + " waiting for hs="
423 + historyStart);
424 mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd));
425 cycleEnd = cycleStart;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700426
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700427 // TODO: remove this guard once we have better testing
428 if (guardCount++ > 50) {
429 Log.wtf(TAG, "stuck generating ranges for bounds=" + Arrays.toString(bounds)
430 + " and policy=" + policy);
431 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700432 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700433
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700434 // one last cycle entry to modify policy cycle day
435 mCycleAdapter.add(new CycleChangeItem(context));
436
437 } else {
438 // no valid cycle; show all data
439 // TODO: offer simple ranges like "last week" etc
440 mCycleAdapter.add(new CycleItem(context, historyStart, historyEnd));
441
442 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700443
444 // force pick the current cycle (first item)
445 mCycleSpinner.setSelection(0);
446 mCycleListener.onItemSelected(mCycleSpinner, null, 0, 0);
447 }
448
449 /**
450 * Force rebind of hijacked {@link Preference} views.
451 */
452 private void refreshPreferenceViews() {
453 mDataEnabledView = mDataEnabled.getView(mDataEnabledView, mListView);
454 mDisableAtLimitView = mDisableAtLimit.getView(mDisableAtLimitView, mListView);
455 }
456
457 private OnClickListener mDataEnabledListener = new OnClickListener() {
458 /** {@inheritDoc} */
459 public void onClick(View v) {
460 mDataEnabled.setChecked(!mDataEnabled.isChecked());
461 refreshPreferenceViews();
462
463 // TODO: wire up to telephony to enable/disable radios
464 }
465 };
466
467 private OnClickListener mDisableAtLimitListener = new OnClickListener() {
468 /** {@inheritDoc} */
469 public void onClick(View v) {
470 final boolean disableAtLimit = !mDisableAtLimit.isChecked();
471 mDisableAtLimit.setChecked(disableAtLimit);
472 refreshPreferenceViews();
473
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700474 // TODO: create policy if none exists
Jeff Sharkey8a503642011-06-10 13:31:21 -0700475 // TODO: show interstitial warning dialog to user
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700476 final long limitBytes = disableAtLimit ? 5 * GB_IN_BYTES : LIMIT_DISABLED;
477 mPolicyModifier.setPolicyLimitBytes(mTemplate, limitBytes);
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700478 updatePolicy(false);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700479 }
480 };
481
482 private OnItemClickListener mListListener = new OnItemClickListener() {
483 /** {@inheritDoc} */
484 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700485 final AppUsageItem app = (AppUsageItem) parent.getItemAtPosition(position);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700486
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700487 final Bundle args = new Bundle();
488 args.putInt(Intent.EXTRA_UID, app.uid);
489
490 final PreferenceActivity activity = (PreferenceActivity) getActivity();
491 activity.startPreferencePanel(DataUsageAppDetail.class.getName(), args,
492 R.string.data_usage_summary_title, null, null, 0);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700493 }
494 };
495
496 private OnItemSelectedListener mCycleListener = new OnItemSelectedListener() {
497 /** {@inheritDoc} */
498 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
499 final CycleItem cycle = (CycleItem) parent.getItemAtPosition(position);
500 if (cycle instanceof CycleChangeItem) {
501 // TODO: show "define cycle" dialog
502 // also reset back to first cycle
503 Log.d(TAG, "CHANGE CYCLE DIALOG!!");
504
505 } else {
506 if (LOGD) Log.d(TAG, "shoiwng cycle " + cycle);
507
508 // update chart to show selected cycle, and update detail data
509 // to match updated sweep bounds.
510 final long[] bounds = getHistoryBounds(mHistory);
511 mChart.setVisibleRange(cycle.start, cycle.end, bounds[1]);
512
513 updateDetailData();
514 }
515 }
516
517 /** {@inheritDoc} */
518 public void onNothingSelected(AdapterView<?> parent) {
519 // ignored
520 }
521 };
522
523 /**
524 * Update {@link #mAdapter} with sorted list of applications data usage,
525 * based on current inspection from {@link #mChart}.
526 */
527 private void updateDetailData() {
528 if (LOGD) Log.d(TAG, "updateDetailData()");
529
530 try {
531 final long[] range = mChart.getInspectRange();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700532 final NetworkStats stats = mStatsService.getSummaryForAllUid(
Jeff Sharkey8a503642011-06-10 13:31:21 -0700533 range[0], range[1], mTemplate);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700534 mAdapter.bindStats(stats);
535 } catch (RemoteException e) {
536 Log.w(TAG, "problem reading stats");
537 }
538 }
539
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700540 private static String getActiveSubscriberId(Context context) {
541 final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
542 Context.TELEPHONY_SERVICE);
543 return telephony.getSubscriberId();
544 }
545
Jeff Sharkey8a503642011-06-10 13:31:21 -0700546 private DataUsageChartListener mChartListener = new DataUsageChartListener() {
547 /** {@inheritDoc} */
548 public void onInspectRangeChanged() {
549 if (LOGD) Log.d(TAG, "onInspectRangeChanged()");
550 updateDetailData();
551 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700552
Jeff Sharkey8a503642011-06-10 13:31:21 -0700553 /** {@inheritDoc} */
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700554 public void onWarningChanged() {
555 if (LOGD) Log.d(TAG, "onWarningChanged()");
Jeff Sharkey8a503642011-06-10 13:31:21 -0700556 final long warningBytes = mChart.getWarningBytes();
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700557 mPolicyModifier.setPolicyWarningBytes(mTemplate, warningBytes);
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700558 updatePolicy(false);
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700559 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700560
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700561 /** {@inheritDoc} */
562 public void onLimitChanged() {
563 if (LOGD) Log.d(TAG, "onLimitChanged()");
564 final long limitBytes = mDisableAtLimit.isChecked() ? mChart.getLimitBytes()
565 : LIMIT_DISABLED;
566 mPolicyModifier.setPolicyLimitBytes(mTemplate, limitBytes);
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700567 updatePolicy(false);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700568 }
569 };
570
571
572 /**
Jeff Sharkey8a503642011-06-10 13:31:21 -0700573 * List item that reflects a specific data usage cycle.
574 */
575 public static class CycleItem {
576 public CharSequence label;
577 public long start;
578 public long end;
579
580 private static final StringBuilder sBuilder = new StringBuilder(50);
581 private static final java.util.Formatter sFormatter = new java.util.Formatter(
582 sBuilder, Locale.getDefault());
583
584 CycleItem(CharSequence label) {
585 this.label = label;
586 }
587
588 public CycleItem(Context context, long start, long end) {
589 this.label = formatDateRangeUtc(context, start, end);
590 this.start = start;
591 this.end = end;
592 }
593
594 private static String formatDateRangeUtc(Context context, long start, long end) {
595 synchronized (sBuilder) {
596 sBuilder.setLength(0);
597 return DateUtils.formatDateRange(context, sFormatter, start, end,
598 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH,
599 Time.TIMEZONE_UTC).toString();
600 }
601 }
602
603 @Override
604 public String toString() {
605 return label.toString();
606 }
607 }
608
609 /**
610 * Special-case data usage cycle that triggers dialog to change
611 * {@link NetworkPolicy#cycleDay}.
612 */
613 public static class CycleChangeItem extends CycleItem {
614 public CycleChangeItem(Context context) {
615 super(context.getString(R.string.data_usage_change_cycle));
616 }
617 }
618
619 public static class CycleAdapter extends ArrayAdapter<CycleItem> {
620 public CycleAdapter(Context context) {
621 super(context, android.R.layout.simple_spinner_item);
622 setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
623 }
624 }
625
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700626 private static class AppUsageItem implements Comparable<AppUsageItem> {
627 public int uid;
628 public long total;
629
630 /** {@inheritDoc} */
631 public int compareTo(AppUsageItem another) {
632 return Long.compare(another.total, total);
633 }
634 }
635
Jeff Sharkey8a503642011-06-10 13:31:21 -0700636 /**
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700637 * Adapter of applications, sorted by total usage descending.
638 */
639 public static class DataUsageAdapter extends BaseAdapter {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700640 private ArrayList<AppUsageItem> mItems = Lists.newArrayList();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700641
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700642 public void bindStats(NetworkStats stats) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700643 mItems.clear();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700644
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700645 for (int i = 0; i < stats.size; i++) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700646 final AppUsageItem item = new AppUsageItem();
647 item.uid = stats.uid[i];
648 item.total = stats.rx[i] + stats.tx[i];
649 mItems.add(item);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700650 }
651
Jeff Sharkey8a503642011-06-10 13:31:21 -0700652 Collections.sort(mItems);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700653 notifyDataSetChanged();
654 }
655
656 @Override
657 public int getCount() {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700658 return mItems.size();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700659 }
660
661 @Override
662 public Object getItem(int position) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700663 return mItems.get(position);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700664 }
665
666 @Override
667 public long getItemId(int position) {
668 return position;
669 }
670
671 @Override
672 public View getView(int position, View convertView, ViewGroup parent) {
673 if (convertView == null) {
674 convertView = LayoutInflater.from(parent.getContext()).inflate(
675 android.R.layout.simple_list_item_2, parent, false);
676 }
677
678 final Context context = parent.getContext();
679 final PackageManager pm = context.getPackageManager();
680
681 final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
682 final TextView text2 = (TextView) convertView.findViewById(android.R.id.text2);
683
Jeff Sharkey8a503642011-06-10 13:31:21 -0700684 final AppUsageItem item = mItems.get(position);
685 text1.setText(pm.getNameForUid(item.uid));
686 text2.setText(Formatter.formatFileSize(context, item.total));
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700687
688 return convertView;
689 }
690
691 }
692
693
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700694}