blob: beb3ed4f81e86f2f82666cec69dbc50f3e255c33 [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 Sharkeyab2d8d32011-05-30 16:19:56 -070035import android.content.pm.PackageManager;
Jeff Sharkey8a503642011-06-10 13:31:21 -070036import android.net.INetworkPolicyManager;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070037import android.net.INetworkStatsService;
Jeff Sharkey8a503642011-06-10 13:31:21 -070038import android.net.NetworkPolicy;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070039import android.net.NetworkStats;
40import android.net.NetworkStatsHistory;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070041import android.os.Bundle;
42import android.os.RemoteException;
43import android.os.ServiceManager;
Jeff Sharkey8a503642011-06-10 13:31:21 -070044import android.preference.CheckBoxPreference;
45import android.preference.Preference;
Jeff Sharkey4dfa6602011-06-13 00:42:03 -070046import android.preference.PreferenceActivity;
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -070047import android.preference.SwitchPreference;
48import android.telephony.TelephonyManager;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070049import android.text.format.DateUtils;
50import android.text.format.Formatter;
Jeff Sharkey8a503642011-06-10 13:31:21 -070051import android.text.format.Time;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070052import android.util.Log;
53import android.view.LayoutInflater;
Jeff Sharkey8a503642011-06-10 13:31:21 -070054import android.view.Menu;
55import android.view.MenuInflater;
56import android.view.MenuItem;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070057import android.view.View;
Jeff Sharkey8a503642011-06-10 13:31:21 -070058import android.view.View.OnClickListener;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070059import android.view.ViewGroup;
Jeff Sharkey8a503642011-06-10 13:31:21 -070060import android.widget.AbsListView;
61import android.widget.AdapterView;
62import android.widget.AdapterView.OnItemClickListener;
63import android.widget.AdapterView.OnItemSelectedListener;
64import android.widget.ArrayAdapter;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070065import android.widget.BaseAdapter;
Jeff Sharkey8a503642011-06-10 13:31:21 -070066import android.widget.LinearLayout;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070067import android.widget.ListView;
Jeff Sharkey4c72ae52011-06-14 15:01:18 -070068import android.widget.NumberPicker;
Jeff Sharkey8a503642011-06-10 13:31:21 -070069import android.widget.Spinner;
70import android.widget.TabHost;
71import android.widget.TabHost.OnTabChangeListener;
72import android.widget.TabHost.TabContentFactory;
73import android.widget.TabHost.TabSpec;
74import android.widget.TabWidget;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070075import android.widget.TextView;
76
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -070077import com.android.settings.net.NetworkPolicyModifier;
Jeff Sharkey8a503642011-06-10 13:31:21 -070078import com.android.settings.widget.DataUsageChartView;
79import com.android.settings.widget.DataUsageChartView.DataUsageChartListener;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070080import com.google.android.collect.Lists;
81
82import java.util.ArrayList;
Jeff Sharkey8a503642011-06-10 13:31:21 -070083import java.util.Arrays;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070084import java.util.Collections;
Jeff Sharkey8a503642011-06-10 13:31:21 -070085import java.util.Locale;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070086
87public class DataUsageSummary extends Fragment {
88 private static final String TAG = "DataUsage";
Jeff Sharkey8a503642011-06-10 13:31:21 -070089 private static final boolean LOGD = true;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070090
Jeff Sharkey8a503642011-06-10 13:31:21 -070091 private static final int TEMPLATE_INVALID = -1;
92
93 private static final String TAB_3G = "3g";
94 private static final String TAB_4G = "4g";
95 private static final String TAB_MOBILE = "mobile";
96 private static final String TAB_WIFI = "wifi";
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070097
Jeff Sharkey4c72ae52011-06-14 15:01:18 -070098 private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
99 private static final String TAG_CYCLE_EDITOR = "cycleEditor";
100
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700101 private static final long KB_IN_BYTES = 1024;
102 private static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
103 private static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
104
105 private INetworkStatsService mStatsService;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700106 private INetworkPolicyManager mPolicyService;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700107
Jeff Sharkey8a503642011-06-10 13:31:21 -0700108 private TabHost mTabHost;
109 private TabWidget mTabWidget;
110 private ListView mListView;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700111 private DataUsageAdapter mAdapter;
112
Jeff Sharkey8a503642011-06-10 13:31:21 -0700113 private View mHeader;
114 private LinearLayout mSwitches;
115
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700116 private SwitchPreference mDataEnabled;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700117 private CheckBoxPreference mDisableAtLimit;
118 private View mDataEnabledView;
119 private View mDisableAtLimitView;
120
121 private DataUsageChartView mChart;
122
123 private Spinner mCycleSpinner;
124 private CycleAdapter mCycleAdapter;
125
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700126 // TODO: persist show wifi flag
Jeff Sharkey8a503642011-06-10 13:31:21 -0700127 private boolean mShowWifi = false;
128
129 private int mTemplate = TEMPLATE_INVALID;
130
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700131 private NetworkPolicyModifier mPolicyModifier;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700132 private NetworkStatsHistory mHistory;
133
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700134 @Override
Jeff Sharkey8a503642011-06-10 13:31:21 -0700135 public void onCreate(Bundle savedInstanceState) {
136 super.onCreate(savedInstanceState);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700137
138 mStatsService = INetworkStatsService.Stub.asInterface(
139 ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
Jeff Sharkey8a503642011-06-10 13:31:21 -0700140 mPolicyService = INetworkPolicyManager.Stub.asInterface(
141 ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700142
143 final Context context = getActivity();
144 final String subscriberId = getActiveSubscriberId(context);
145 mPolicyModifier = new NetworkPolicyModifier(mPolicyService, subscriberId);
Jeff Sharkey94a90952011-06-13 22:31:09 -0700146 mPolicyModifier.read();
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700147
148 setHasOptionsMenu(true);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700149 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700150
Jeff Sharkey8a503642011-06-10 13:31:21 -0700151 @Override
152 public View onCreateView(LayoutInflater inflater, ViewGroup container,
153 Bundle savedInstanceState) {
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700154
Jeff Sharkey8a503642011-06-10 13:31:21 -0700155 final Context context = inflater.getContext();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700156 final View view = inflater.inflate(R.layout.data_usage_summary, container, false);
157
Jeff Sharkey8a503642011-06-10 13:31:21 -0700158 mTabHost = (TabHost) view.findViewById(android.R.id.tabhost);
159 mTabWidget = (TabWidget) view.findViewById(android.R.id.tabs);
160 mListView = (ListView) view.findViewById(android.R.id.list);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700161
Jeff Sharkey8a503642011-06-10 13:31:21 -0700162 mTabHost.setup();
163 mTabHost.setOnTabChangedListener(mTabListener);
164
165 mHeader = inflater.inflate(R.layout.data_usage_header, mListView, false);
166 mListView.addHeaderView(mHeader, null, false);
167
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700168 mDataEnabled = new SwitchPreference(context);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700169 mDisableAtLimit = new CheckBoxPreference(context);
170
171 // kick refresh once to force-create views
172 refreshPreferenceViews();
173
174 // TODO: remove once thin preferences are supported (48dip)
175 mDataEnabledView.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, 72));
176 mDisableAtLimitView.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, 72));
177
178 mDataEnabledView.setOnClickListener(mDataEnabledListener);
179 mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener);
180
181 mSwitches = (LinearLayout) mHeader.findViewById(R.id.switches);
182 mSwitches.addView(mDataEnabledView);
183 mSwitches.addView(mDisableAtLimitView);
184
185 mCycleSpinner = (Spinner) mHeader.findViewById(R.id.cycles);
186 mCycleAdapter = new CycleAdapter(context);
187 mCycleSpinner.setAdapter(mCycleAdapter);
188 mCycleSpinner.setOnItemSelectedListener(mCycleListener);
189
190 mChart = new DataUsageChartView(context);
191 mChart.setListener(mChartListener);
192 mChart.setLayoutParams(new AbsListView.LayoutParams(MATCH_PARENT, 350));
193 mListView.addHeaderView(mChart, null, false);
194
195 mAdapter = new DataUsageAdapter();
196 mListView.setOnItemClickListener(mListListener);
197 mListView.setAdapter(mAdapter);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700198
199 return view;
200 }
201
202 @Override
203 public void onResume() {
204 super.onResume();
205
Jeff Sharkey8a503642011-06-10 13:31:21 -0700206 // this kicks off chain reaction which creates tabs, binds the body to
207 // selected network, and binds chart, cycles and detail list.
208 updateTabs();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700209 }
210
Jeff Sharkey8a503642011-06-10 13:31:21 -0700211 @Override
212 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
213 inflater.inflate(R.menu.data_usage, menu);
214 }
215
216 @Override
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700217 public void onPrepareOptionsMenu(Menu menu) {
218 final MenuItem split4g = menu.findItem(R.id.action_split_4g);
219 split4g.setChecked(mPolicyModifier.isMobilePolicySplit());
220 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700221
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700222 @Override
223 public boolean onOptionsItemSelected(MenuItem item) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700224 switch (item.getItemId()) {
225 case R.id.action_split_4g: {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700226 final boolean mobileSplit = !item.isChecked();
227 mPolicyModifier.setMobilePolicySplit(mobileSplit);
228 item.setChecked(mPolicyModifier.isMobilePolicySplit());
Jeff Sharkey8a503642011-06-10 13:31:21 -0700229 updateTabs();
230 return true;
231 }
232 case R.id.action_show_wifi: {
233 mShowWifi = !item.isChecked();
234 item.setChecked(mShowWifi);
235 updateTabs();
236 return true;
237 }
238 }
239 return false;
240 }
241
Jeff Sharkey94a90952011-06-13 22:31:09 -0700242 @Override
243 public void onDestroyView() {
244 super.onDestroyView();
245
246 mDataEnabledView = null;
247 mDisableAtLimitView = null;
248 }
249
Jeff Sharkey8a503642011-06-10 13:31:21 -0700250 /**
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700251 * Rebuild all tabs based on {@link NetworkPolicyModifier} and
252 * {@link #mShowWifi}, hiding the tabs entirely when applicable. Selects
253 * first tab, and kicks off a full rebind of body contents.
Jeff Sharkey8a503642011-06-10 13:31:21 -0700254 */
255 private void updateTabs() {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700256 final boolean mobileSplit = mPolicyModifier.isMobilePolicySplit();
257 final boolean tabsVisible = mobileSplit || mShowWifi;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700258 mTabWidget.setVisibility(tabsVisible ? View.VISIBLE : View.GONE);
259 mTabHost.clearAllTabs();
260
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700261 if (mobileSplit) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700262 mTabHost.addTab(buildTabSpec(TAB_3G, R.string.data_usage_tab_3g));
263 mTabHost.addTab(buildTabSpec(TAB_4G, R.string.data_usage_tab_4g));
264 }
265
266 if (mShowWifi) {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700267 if (!mobileSplit) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700268 mTabHost.addTab(buildTabSpec(TAB_MOBILE, R.string.data_usage_tab_mobile));
269 }
270 mTabHost.addTab(buildTabSpec(TAB_WIFI, R.string.data_usage_tab_wifi));
271 }
272
273 if (mTabWidget.getTabCount() > 0) {
274 // select first tab, which will kick off updateBody()
275 mTabHost.setCurrentTab(0);
276 } else {
277 // no tabs shown; update body manually
278 updateBody();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700279 }
280 }
281
Jeff Sharkey8a503642011-06-10 13:31:21 -0700282 /**
283 * Factory that provide empty {@link View} to make {@link TabHost} happy.
284 */
285 private TabContentFactory mEmptyTabContent = new TabContentFactory() {
286 /** {@inheritDoc} */
287 public View createTabContent(String tag) {
288 return new View(mTabHost.getContext());
289 }
290 };
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700291
Jeff Sharkey8a503642011-06-10 13:31:21 -0700292 /**
293 * Build {@link TabSpec} with thin indicator, and empty content.
294 */
295 private TabSpec buildTabSpec(String tag, int titleRes) {
296 final LayoutInflater inflater = LayoutInflater.from(mTabWidget.getContext());
297 final View indicator = inflater.inflate(
298 R.layout.tab_indicator_thin_holo, mTabWidget, false);
299 final TextView title = (TextView) indicator.findViewById(android.R.id.title);
300 title.setText(titleRes);
301 return mTabHost.newTabSpec(tag).setIndicator(indicator).setContent(mEmptyTabContent);
302 }
303
304 private OnTabChangeListener mTabListener = new OnTabChangeListener() {
305 /** {@inheritDoc} */
306 public void onTabChanged(String tabId) {
307 // user changed tab; update body
308 updateBody();
309 }
310 };
311
312 /**
313 * Update body content based on current tab. Loads
314 * {@link NetworkStatsHistory} and {@link NetworkPolicy} from system, and
315 * binds them to visible controls.
316 */
317 private void updateBody() {
318 final String tabTag = mTabHost.getCurrentTabTag();
319 final String currentTab = tabTag != null ? tabTag : TAB_MOBILE;
320
321 if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab);
322
323 if (TAB_WIFI.equals(currentTab)) {
324 // wifi doesn't have any controls
325 mDataEnabledView.setVisibility(View.GONE);
326 mDisableAtLimitView.setVisibility(View.GONE);
327 mTemplate = TEMPLATE_WIFI;
328
329 } else {
330 // make sure we show for non-wifi
331 mDataEnabledView.setVisibility(View.VISIBLE);
332 mDisableAtLimitView.setVisibility(View.VISIBLE);
333 }
334
335 if (TAB_MOBILE.equals(currentTab)) {
336 mDataEnabled.setTitle(R.string.data_usage_enable_mobile);
337 mDisableAtLimit.setTitle(R.string.data_usage_disable_mobile_limit);
338 mTemplate = TEMPLATE_MOBILE_ALL;
339
340 } else if (TAB_3G.equals(currentTab)) {
341 mDataEnabled.setTitle(R.string.data_usage_enable_3g);
342 mDisableAtLimit.setTitle(R.string.data_usage_disable_3g_limit);
343 mTemplate = TEMPLATE_MOBILE_3G_LOWER;
344
345 } else if (TAB_4G.equals(currentTab)) {
346 mDataEnabled.setTitle(R.string.data_usage_enable_4g);
347 mDisableAtLimit.setTitle(R.string.data_usage_disable_4g_limit);
348 mTemplate = TEMPLATE_MOBILE_4G;
349
350 }
351
352 // TODO: populate checkbox based on radio preferences
353 mDataEnabled.setChecked(true);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700354
355 try {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700356 // load stats for current template
Jeff Sharkey8a503642011-06-10 13:31:21 -0700357 mHistory = mStatsService.getHistoryForNetwork(mTemplate);
358 } catch (RemoteException e) {
359 // since we can't do much without policy or history, and we don't
360 // want to leave with half-baked UI, we bail hard.
361 throw new RuntimeException("problem reading network policy or stats", e);
362 }
363
Jeff Sharkey8a503642011-06-10 13:31:21 -0700364 // bind chart to historical stats
Jeff Sharkey8a503642011-06-10 13:31:21 -0700365 mChart.bindNetworkStats(mHistory);
366
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700367 updatePolicy(true);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700368
369 // force scroll to top of body
370 mListView.smoothScrollToPosition(0);
371
372 // kick preference views so they rebind from changes above
373 refreshPreferenceViews();
374 }
375
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700376 private void setPolicyCycleDay(int cycleDay) {
377 if (LOGD) Log.d(TAG, "setPolicyCycleDay()");
378 mPolicyModifier.setPolicyCycleDay(mTemplate, cycleDay);
379 updatePolicy(true);
380 }
381
382 private void setPolicyWarningBytes(long warningBytes) {
383 if (LOGD) Log.d(TAG, "setPolicyWarningBytes()");
384 mPolicyModifier.setPolicyWarningBytes(mTemplate, warningBytes);
385 updatePolicy(false);
386 }
387
388 private void setPolicyLimitBytes(long limitBytes) {
389 if (LOGD) Log.d(TAG, "setPolicyLimitBytes()");
390 mPolicyModifier.setPolicyLimitBytes(mTemplate, limitBytes);
391 updatePolicy(false);
392 }
393
Jeff Sharkey8a503642011-06-10 13:31:21 -0700394 /**
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700395 * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for
396 * current {@link #mTemplate}.
397 */
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700398 private void updatePolicy(boolean refreshCycle) {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700399 final NetworkPolicy policy = mPolicyModifier.getPolicy(mTemplate);
400
401 // reflect policy limit in checkbox
402 mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED);
403 mChart.bindNetworkPolicy(policy);
404
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700405 if (refreshCycle) {
406 // generate cycle list based on policy and available history
407 updateCycleList(policy);
408 }
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700409
410 // kick preference views so they rebind from changes above
411 refreshPreferenceViews();
412 }
413
414 /**
Jeff Sharkey8a503642011-06-10 13:31:21 -0700415 * Return full time bounds (earliest and latest time recorded) of the given
416 * {@link NetworkStatsHistory}.
417 */
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700418 public static long[] getHistoryBounds(NetworkStatsHistory history) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700419 final long currentTime = System.currentTimeMillis();
420
421 long start = currentTime;
422 long end = currentTime;
423 if (history.bucketCount > 0) {
424 start = history.bucketStart[0];
425 end = history.bucketStart[history.bucketCount - 1];
426 }
427
428 return new long[] { start, end };
429 }
430
431 /**
432 * Rebuild {@link #mCycleAdapter} based on {@link NetworkPolicy#cycleDay}
433 * and available {@link NetworkStatsHistory} data. Always selects the newest
434 * item, updating the inspection range on {@link #mChart}.
435 */
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700436 private void updateCycleList(NetworkPolicy policy) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700437 mCycleAdapter.clear();
438
439 final Context context = mCycleSpinner.getContext();
440
441 final long[] bounds = getHistoryBounds(mHistory);
442 final long historyStart = bounds[0];
443 final long historyEnd = bounds[1];
444
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700445 if (policy != null) {
446 // find the next cycle boundary
447 long cycleEnd = computeNextCycleBoundary(historyEnd, policy);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700448
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700449 int guardCount = 0;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700450
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700451 // walk backwards, generating all valid cycle ranges
452 while (cycleEnd > historyStart) {
453 final long cycleStart = computeLastCycleBoundary(cycleEnd, policy);
454 Log.d(TAG, "generating cs=" + cycleStart + " to ce=" + cycleEnd + " waiting for hs="
455 + historyStart);
456 mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd));
457 cycleEnd = cycleStart;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700458
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700459 // TODO: remove this guard once we have better testing
460 if (guardCount++ > 50) {
461 Log.wtf(TAG, "stuck generating ranges for bounds=" + Arrays.toString(bounds)
462 + " and policy=" + policy);
463 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700464 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700465
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700466 // one last cycle entry to modify policy cycle day
467 mCycleAdapter.add(new CycleChangeItem(context));
468
469 } else {
470 // no valid cycle; show all data
471 // TODO: offer simple ranges like "last week" etc
472 mCycleAdapter.add(new CycleItem(context, historyStart, historyEnd));
473
474 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700475
476 // force pick the current cycle (first item)
477 mCycleSpinner.setSelection(0);
478 mCycleListener.onItemSelected(mCycleSpinner, null, 0, 0);
479 }
480
481 /**
482 * Force rebind of hijacked {@link Preference} views.
483 */
484 private void refreshPreferenceViews() {
485 mDataEnabledView = mDataEnabled.getView(mDataEnabledView, mListView);
486 mDisableAtLimitView = mDisableAtLimit.getView(mDisableAtLimitView, mListView);
487 }
488
489 private OnClickListener mDataEnabledListener = new OnClickListener() {
490 /** {@inheritDoc} */
491 public void onClick(View v) {
492 mDataEnabled.setChecked(!mDataEnabled.isChecked());
493 refreshPreferenceViews();
494
495 // TODO: wire up to telephony to enable/disable radios
496 }
497 };
498
499 private OnClickListener mDisableAtLimitListener = new OnClickListener() {
500 /** {@inheritDoc} */
501 public void onClick(View v) {
502 final boolean disableAtLimit = !mDisableAtLimit.isChecked();
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700503 if (disableAtLimit) {
504 // enabling limit; show confirmation dialog which eventually
505 // calls setPolicyLimitBytes() once user confirms.
506 ConfirmLimitFragment.show(DataUsageSummary.this);
507 } else {
508 setPolicyLimitBytes(LIMIT_DISABLED);
509 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700510 }
511 };
512
513 private OnItemClickListener mListListener = new OnItemClickListener() {
514 /** {@inheritDoc} */
515 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700516 final AppUsageItem app = (AppUsageItem) parent.getItemAtPosition(position);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700517
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700518 final Bundle args = new Bundle();
519 args.putInt(Intent.EXTRA_UID, app.uid);
520
521 final PreferenceActivity activity = (PreferenceActivity) getActivity();
522 activity.startPreferencePanel(DataUsageAppDetail.class.getName(), args,
523 R.string.data_usage_summary_title, null, null, 0);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700524 }
525 };
526
527 private OnItemSelectedListener mCycleListener = new OnItemSelectedListener() {
528 /** {@inheritDoc} */
529 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
530 final CycleItem cycle = (CycleItem) parent.getItemAtPosition(position);
531 if (cycle instanceof CycleChangeItem) {
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700532 // show cycle editor; will eventually call setPolicyCycleDay()
533 // when user finishes editing.
534 CycleEditorFragment.show(DataUsageSummary.this);
535
536 // reset spinner to something other than "change cycle..."
537 mCycleSpinner.setSelection(0);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700538
539 } else {
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700540 if (LOGD) Log.d(TAG, "showing cycle " + cycle);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700541
542 // update chart to show selected cycle, and update detail data
543 // to match updated sweep bounds.
544 final long[] bounds = getHistoryBounds(mHistory);
545 mChart.setVisibleRange(cycle.start, cycle.end, bounds[1]);
546
547 updateDetailData();
548 }
549 }
550
551 /** {@inheritDoc} */
552 public void onNothingSelected(AdapterView<?> parent) {
553 // ignored
554 }
555 };
556
557 /**
558 * Update {@link #mAdapter} with sorted list of applications data usage,
559 * based on current inspection from {@link #mChart}.
560 */
561 private void updateDetailData() {
562 if (LOGD) Log.d(TAG, "updateDetailData()");
563
564 try {
565 final long[] range = mChart.getInspectRange();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700566 final NetworkStats stats = mStatsService.getSummaryForAllUid(
Jeff Sharkey8a503642011-06-10 13:31:21 -0700567 range[0], range[1], mTemplate);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700568 mAdapter.bindStats(stats);
569 } catch (RemoteException e) {
570 Log.w(TAG, "problem reading stats");
571 }
572 }
573
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700574 private static String getActiveSubscriberId(Context context) {
575 final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
576 Context.TELEPHONY_SERVICE);
577 return telephony.getSubscriberId();
578 }
579
Jeff Sharkey8a503642011-06-10 13:31:21 -0700580 private DataUsageChartListener mChartListener = new DataUsageChartListener() {
581 /** {@inheritDoc} */
582 public void onInspectRangeChanged() {
583 if (LOGD) Log.d(TAG, "onInspectRangeChanged()");
584 updateDetailData();
585 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700586
Jeff Sharkey8a503642011-06-10 13:31:21 -0700587 /** {@inheritDoc} */
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700588 public void onWarningChanged() {
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700589 setPolicyWarningBytes(mChart.getWarningBytes());
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700590 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700591
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700592 /** {@inheritDoc} */
593 public void onLimitChanged() {
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700594 setPolicyLimitBytes(mChart.getLimitBytes());
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700595 }
596 };
597
598
599 /**
Jeff Sharkey8a503642011-06-10 13:31:21 -0700600 * List item that reflects a specific data usage cycle.
601 */
602 public static class CycleItem {
603 public CharSequence label;
604 public long start;
605 public long end;
606
607 private static final StringBuilder sBuilder = new StringBuilder(50);
608 private static final java.util.Formatter sFormatter = new java.util.Formatter(
609 sBuilder, Locale.getDefault());
610
611 CycleItem(CharSequence label) {
612 this.label = label;
613 }
614
615 public CycleItem(Context context, long start, long end) {
616 this.label = formatDateRangeUtc(context, start, end);
617 this.start = start;
618 this.end = end;
619 }
620
621 private static String formatDateRangeUtc(Context context, long start, long end) {
622 synchronized (sBuilder) {
623 sBuilder.setLength(0);
624 return DateUtils.formatDateRange(context, sFormatter, start, end,
625 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH,
626 Time.TIMEZONE_UTC).toString();
627 }
628 }
629
630 @Override
631 public String toString() {
632 return label.toString();
633 }
634 }
635
636 /**
637 * Special-case data usage cycle that triggers dialog to change
638 * {@link NetworkPolicy#cycleDay}.
639 */
640 public static class CycleChangeItem extends CycleItem {
641 public CycleChangeItem(Context context) {
642 super(context.getString(R.string.data_usage_change_cycle));
643 }
644 }
645
646 public static class CycleAdapter extends ArrayAdapter<CycleItem> {
647 public CycleAdapter(Context context) {
648 super(context, android.R.layout.simple_spinner_item);
649 setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
650 }
651 }
652
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700653 private static class AppUsageItem implements Comparable<AppUsageItem> {
654 public int uid;
655 public long total;
656
657 /** {@inheritDoc} */
658 public int compareTo(AppUsageItem another) {
659 return Long.compare(another.total, total);
660 }
661 }
662
Jeff Sharkey8a503642011-06-10 13:31:21 -0700663 /**
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700664 * Adapter of applications, sorted by total usage descending.
665 */
666 public static class DataUsageAdapter extends BaseAdapter {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700667 private ArrayList<AppUsageItem> mItems = Lists.newArrayList();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700668
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700669 public void bindStats(NetworkStats stats) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700670 mItems.clear();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700671
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700672 for (int i = 0; i < stats.size; i++) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700673 final AppUsageItem item = new AppUsageItem();
674 item.uid = stats.uid[i];
675 item.total = stats.rx[i] + stats.tx[i];
676 mItems.add(item);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700677 }
678
Jeff Sharkey8a503642011-06-10 13:31:21 -0700679 Collections.sort(mItems);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700680 notifyDataSetChanged();
681 }
682
683 @Override
684 public int getCount() {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700685 return mItems.size();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700686 }
687
688 @Override
689 public Object getItem(int position) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700690 return mItems.get(position);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700691 }
692
693 @Override
694 public long getItemId(int position) {
695 return position;
696 }
697
698 @Override
699 public View getView(int position, View convertView, ViewGroup parent) {
700 if (convertView == null) {
701 convertView = LayoutInflater.from(parent.getContext()).inflate(
702 android.R.layout.simple_list_item_2, parent, false);
703 }
704
705 final Context context = parent.getContext();
706 final PackageManager pm = context.getPackageManager();
707
708 final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
709 final TextView text2 = (TextView) convertView.findViewById(android.R.id.text2);
710
Jeff Sharkey8a503642011-06-10 13:31:21 -0700711 final AppUsageItem item = mItems.get(position);
712 text1.setText(pm.getNameForUid(item.uid));
713 text2.setText(Formatter.formatFileSize(context, item.total));
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700714
715 return convertView;
716 }
717
718 }
719
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700720 /**
721 * Dialog to request user confirmation before setting
722 * {@link NetworkPolicy#limitBytes}.
723 */
724 public static class ConfirmLimitFragment extends DialogFragment {
725 public static final String EXTRA_MESSAGE_ID = "messageId";
726 public static final String EXTRA_LIMIT_BYTES = "limitBytes";
727
728 public static void show(DataUsageSummary parent) {
729 final Bundle args = new Bundle();
730
731 // TODO: customize default limits based on network template
732 switch (parent.mTemplate) {
733 case TEMPLATE_MOBILE_3G_LOWER: {
734 args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_3g);
735 args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES);
736 break;
737 }
738 case TEMPLATE_MOBILE_4G: {
739 args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_4g);
740 args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES);
741 break;
742 }
743 case TEMPLATE_MOBILE_ALL: {
744 args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_mobile);
745 args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES);
746 break;
747 }
748 }
749
750 final ConfirmLimitFragment dialog = new ConfirmLimitFragment();
751 dialog.setArguments(args);
752 dialog.setTargetFragment(parent, 0);
753 dialog.show(parent.getFragmentManager(), TAG_CONFIRM_LIMIT);
754 }
755
756 @Override
757 public Dialog onCreateDialog(Bundle savedInstanceState) {
758 final Context context = getActivity();
759
760 final int messageId = getArguments().getInt(EXTRA_MESSAGE_ID);
761 final long limitBytes = getArguments().getLong(EXTRA_LIMIT_BYTES);
762
763 final AlertDialog.Builder builder = new AlertDialog.Builder(context);
764 builder.setTitle(R.string.data_usage_limit_dialog_title);
765 builder.setMessage(messageId);
766
767 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
768 public void onClick(DialogInterface dialog, int which) {
769 final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
770 if (target != null) {
771 target.setPolicyLimitBytes(limitBytes);
772 }
773 }
774 });
775
776 return builder.create();
777 }
778 }
779
780 /**
781 * Dialog to edit {@link NetworkPolicy#cycleDay}.
782 */
783 public static class CycleEditorFragment extends DialogFragment {
784 public static final String EXTRA_CYCLE_DAY = "cycleDay";
785
786 public static void show(DataUsageSummary parent) {
787 final NetworkPolicy policy = parent.mPolicyModifier.getPolicy(parent.mTemplate);
788 final Bundle args = new Bundle();
789 args.putInt(CycleEditorFragment.EXTRA_CYCLE_DAY, policy.cycleDay);
790
791 final CycleEditorFragment dialog = new CycleEditorFragment();
792 dialog.setArguments(args);
793 dialog.setTargetFragment(parent, 0);
794 dialog.show(parent.getFragmentManager(), TAG_CYCLE_EDITOR);
795 }
796
797 @Override
798 public Dialog onCreateDialog(Bundle savedInstanceState) {
799 final Context context = getActivity();
800
801 final AlertDialog.Builder builder = new AlertDialog.Builder(context);
802 final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
803
804 final View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false);
805 final NumberPicker cycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day);
806
807 final int oldCycleDay = getArguments().getInt(EXTRA_CYCLE_DAY, 1);
808
809 cycleDayPicker.setMinValue(1);
810 cycleDayPicker.setMaxValue(31);
811 cycleDayPicker.setValue(oldCycleDay);
812 cycleDayPicker.setWrapSelectorWheel(true);
813
814 builder.setTitle(R.string.data_usage_cycle_editor_title);
815 builder.setView(view);
816
817 builder.setPositiveButton(R.string.data_usage_cycle_editor_positive,
818 new DialogInterface.OnClickListener() {
819 public void onClick(DialogInterface dialog, int which) {
820 final int cycleDay = cycleDayPicker.getValue();
821 final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
822 if (target != null) {
823 target.setPolicyCycleDay(cycleDay);
824 }
825 }
826 });
827
828 return builder.create();
829 }
830 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700831
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700832}