blob: 8849f2d12201405041bf64dbf27767de680b2cfe [file] [log] [blame]
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -07001/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.settings;
18
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -070019import static android.net.NetworkPolicy.LIMIT_DISABLED;
Jeff Sharkey8a503642011-06-10 13:31:21 -070020import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
21import static android.net.NetworkPolicyManager.computeNextCycleBoundary;
22import static android.net.TrafficStats.TEMPLATE_MOBILE_3G_LOWER;
23import static android.net.TrafficStats.TEMPLATE_MOBILE_4G;
24import static android.net.TrafficStats.TEMPLATE_MOBILE_ALL;
25import static android.net.TrafficStats.TEMPLATE_WIFI;
26import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070027
Jeff Sharkey4c72ae52011-06-14 15:01:18 -070028import android.app.AlertDialog;
29import android.app.Dialog;
30import android.app.DialogFragment;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070031import android.app.Fragment;
32import android.content.Context;
Jeff Sharkey4c72ae52011-06-14 15:01:18 -070033import android.content.DialogInterface;
Jeff Sharkey4dfa6602011-06-13 00:42:03 -070034import android.content.Intent;
Jeff Sharkey8e911d72011-06-14 22:41:21 -070035import android.content.pm.ApplicationInfo;
36import android.content.pm.PackageInfo;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070037import android.content.pm.PackageManager;
Jeff Sharkey8e911d72011-06-14 22:41:21 -070038import android.content.pm.PackageManager.NameNotFoundException;
Jeff Sharkey8a503642011-06-10 13:31:21 -070039import android.net.INetworkPolicyManager;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070040import android.net.INetworkStatsService;
Jeff Sharkey8a503642011-06-10 13:31:21 -070041import android.net.NetworkPolicy;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070042import android.net.NetworkStats;
43import android.net.NetworkStatsHistory;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070044import android.os.Bundle;
45import android.os.RemoteException;
46import android.os.ServiceManager;
Jeff Sharkey8a503642011-06-10 13:31:21 -070047import android.preference.CheckBoxPreference;
48import android.preference.Preference;
Jeff Sharkey4dfa6602011-06-13 00:42:03 -070049import android.preference.PreferenceActivity;
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -070050import android.preference.SwitchPreference;
51import android.telephony.TelephonyManager;
Jeff Sharkey8e911d72011-06-14 22:41:21 -070052import android.text.TextUtils;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070053import android.text.format.DateUtils;
54import android.text.format.Formatter;
Jeff Sharkey8a503642011-06-10 13:31:21 -070055import android.text.format.Time;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070056import android.util.Log;
57import android.view.LayoutInflater;
Jeff Sharkey8a503642011-06-10 13:31:21 -070058import android.view.Menu;
59import android.view.MenuInflater;
60import android.view.MenuItem;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070061import android.view.View;
Jeff Sharkey8a503642011-06-10 13:31:21 -070062import android.view.View.OnClickListener;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070063import android.view.ViewGroup;
Jeff Sharkey8a503642011-06-10 13:31:21 -070064import android.widget.AbsListView;
65import android.widget.AdapterView;
66import android.widget.AdapterView.OnItemClickListener;
67import android.widget.AdapterView.OnItemSelectedListener;
68import android.widget.ArrayAdapter;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070069import android.widget.BaseAdapter;
Jeff Sharkey8a503642011-06-10 13:31:21 -070070import android.widget.LinearLayout;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070071import android.widget.ListView;
Jeff Sharkey4c72ae52011-06-14 15:01:18 -070072import android.widget.NumberPicker;
Jeff Sharkey8a503642011-06-10 13:31:21 -070073import android.widget.Spinner;
74import android.widget.TabHost;
75import android.widget.TabHost.OnTabChangeListener;
76import android.widget.TabHost.TabContentFactory;
77import android.widget.TabHost.TabSpec;
78import android.widget.TabWidget;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070079import android.widget.TextView;
80
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -070081import com.android.settings.net.NetworkPolicyModifier;
Jeff Sharkey8a503642011-06-10 13:31:21 -070082import com.android.settings.widget.DataUsageChartView;
83import com.android.settings.widget.DataUsageChartView.DataUsageChartListener;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070084import com.google.android.collect.Lists;
85
86import java.util.ArrayList;
Jeff Sharkey8a503642011-06-10 13:31:21 -070087import java.util.Arrays;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070088import java.util.Collections;
Jeff Sharkey8a503642011-06-10 13:31:21 -070089import java.util.Locale;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070090
91public class DataUsageSummary extends Fragment {
92 private static final String TAG = "DataUsage";
Jeff Sharkey8a503642011-06-10 13:31:21 -070093 private static final boolean LOGD = true;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070094
Jeff Sharkey8a503642011-06-10 13:31:21 -070095 private static final int TEMPLATE_INVALID = -1;
96
97 private static final String TAB_3G = "3g";
98 private static final String TAB_4G = "4g";
99 private static final String TAB_MOBILE = "mobile";
100 private static final String TAB_WIFI = "wifi";
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700101
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700102 private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
103 private static final String TAG_CYCLE_EDITOR = "cycleEditor";
104
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700105 private static final long KB_IN_BYTES = 1024;
106 private static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
107 private static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
108
109 private INetworkStatsService mStatsService;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700110 private INetworkPolicyManager mPolicyService;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700111
Jeff Sharkey8a503642011-06-10 13:31:21 -0700112 private TabHost mTabHost;
113 private TabWidget mTabWidget;
114 private ListView mListView;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700115 private DataUsageAdapter mAdapter;
116
Jeff Sharkey8a503642011-06-10 13:31:21 -0700117 private View mHeader;
118 private LinearLayout mSwitches;
119
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700120 private SwitchPreference mDataEnabled;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700121 private CheckBoxPreference mDisableAtLimit;
122 private View mDataEnabledView;
123 private View mDisableAtLimitView;
124
125 private DataUsageChartView mChart;
126
127 private Spinner mCycleSpinner;
128 private CycleAdapter mCycleAdapter;
129
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700130 // TODO: persist show wifi flag
Jeff Sharkey8a503642011-06-10 13:31:21 -0700131 private boolean mShowWifi = false;
132
133 private int mTemplate = TEMPLATE_INVALID;
134
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700135 private NetworkPolicyModifier mPolicyModifier;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700136 private NetworkStatsHistory mHistory;
137
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700138 @Override
Jeff Sharkey8a503642011-06-10 13:31:21 -0700139 public void onCreate(Bundle savedInstanceState) {
140 super.onCreate(savedInstanceState);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700141
142 mStatsService = INetworkStatsService.Stub.asInterface(
143 ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
Jeff Sharkey8a503642011-06-10 13:31:21 -0700144 mPolicyService = INetworkPolicyManager.Stub.asInterface(
145 ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700146
147 final Context context = getActivity();
148 final String subscriberId = getActiveSubscriberId(context);
149 mPolicyModifier = new NetworkPolicyModifier(mPolicyService, subscriberId);
Jeff Sharkey94a90952011-06-13 22:31:09 -0700150 mPolicyModifier.read();
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700151
152 setHasOptionsMenu(true);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700153 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700154
Jeff Sharkey8a503642011-06-10 13:31:21 -0700155 @Override
156 public View onCreateView(LayoutInflater inflater, ViewGroup container,
157 Bundle savedInstanceState) {
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700158
Jeff Sharkey8a503642011-06-10 13:31:21 -0700159 final Context context = inflater.getContext();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700160 final View view = inflater.inflate(R.layout.data_usage_summary, container, false);
161
Jeff Sharkey8a503642011-06-10 13:31:21 -0700162 mTabHost = (TabHost) view.findViewById(android.R.id.tabhost);
163 mTabWidget = (TabWidget) view.findViewById(android.R.id.tabs);
164 mListView = (ListView) view.findViewById(android.R.id.list);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700165
Jeff Sharkey8a503642011-06-10 13:31:21 -0700166 mTabHost.setup();
167 mTabHost.setOnTabChangedListener(mTabListener);
168
169 mHeader = inflater.inflate(R.layout.data_usage_header, mListView, false);
170 mListView.addHeaderView(mHeader, null, false);
171
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700172 mDataEnabled = new SwitchPreference(context);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700173 mDisableAtLimit = new CheckBoxPreference(context);
174
175 // kick refresh once to force-create views
176 refreshPreferenceViews();
177
178 // TODO: remove once thin preferences are supported (48dip)
179 mDataEnabledView.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, 72));
180 mDisableAtLimitView.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, 72));
181
182 mDataEnabledView.setOnClickListener(mDataEnabledListener);
183 mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener);
184
185 mSwitches = (LinearLayout) mHeader.findViewById(R.id.switches);
186 mSwitches.addView(mDataEnabledView);
187 mSwitches.addView(mDisableAtLimitView);
188
189 mCycleSpinner = (Spinner) mHeader.findViewById(R.id.cycles);
190 mCycleAdapter = new CycleAdapter(context);
191 mCycleSpinner.setAdapter(mCycleAdapter);
192 mCycleSpinner.setOnItemSelectedListener(mCycleListener);
193
194 mChart = new DataUsageChartView(context);
195 mChart.setListener(mChartListener);
196 mChart.setLayoutParams(new AbsListView.LayoutParams(MATCH_PARENT, 350));
197 mListView.addHeaderView(mChart, null, false);
198
199 mAdapter = new DataUsageAdapter();
200 mListView.setOnItemClickListener(mListListener);
201 mListView.setAdapter(mAdapter);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700202
203 return view;
204 }
205
206 @Override
207 public void onResume() {
208 super.onResume();
209
Jeff Sharkey8a503642011-06-10 13:31:21 -0700210 // this kicks off chain reaction which creates tabs, binds the body to
211 // selected network, and binds chart, cycles and detail list.
212 updateTabs();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700213 }
214
Jeff Sharkey8a503642011-06-10 13:31:21 -0700215 @Override
216 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
217 inflater.inflate(R.menu.data_usage, menu);
218 }
219
220 @Override
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700221 public void onPrepareOptionsMenu(Menu menu) {
222 final MenuItem split4g = menu.findItem(R.id.action_split_4g);
223 split4g.setChecked(mPolicyModifier.isMobilePolicySplit());
224 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700225
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700226 @Override
227 public boolean onOptionsItemSelected(MenuItem item) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700228 switch (item.getItemId()) {
229 case R.id.action_split_4g: {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700230 final boolean mobileSplit = !item.isChecked();
231 mPolicyModifier.setMobilePolicySplit(mobileSplit);
232 item.setChecked(mPolicyModifier.isMobilePolicySplit());
Jeff Sharkey8a503642011-06-10 13:31:21 -0700233 updateTabs();
234 return true;
235 }
236 case R.id.action_show_wifi: {
237 mShowWifi = !item.isChecked();
238 item.setChecked(mShowWifi);
239 updateTabs();
240 return true;
241 }
242 }
243 return false;
244 }
245
Jeff Sharkey94a90952011-06-13 22:31:09 -0700246 @Override
247 public void onDestroyView() {
248 super.onDestroyView();
249
250 mDataEnabledView = null;
251 mDisableAtLimitView = null;
252 }
253
Jeff Sharkey8a503642011-06-10 13:31:21 -0700254 /**
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700255 * Rebuild all tabs based on {@link NetworkPolicyModifier} and
256 * {@link #mShowWifi}, hiding the tabs entirely when applicable. Selects
257 * first tab, and kicks off a full rebind of body contents.
Jeff Sharkey8a503642011-06-10 13:31:21 -0700258 */
259 private void updateTabs() {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700260 final boolean mobileSplit = mPolicyModifier.isMobilePolicySplit();
261 final boolean tabsVisible = mobileSplit || mShowWifi;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700262 mTabWidget.setVisibility(tabsVisible ? View.VISIBLE : View.GONE);
263 mTabHost.clearAllTabs();
264
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700265 if (mobileSplit) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700266 mTabHost.addTab(buildTabSpec(TAB_3G, R.string.data_usage_tab_3g));
267 mTabHost.addTab(buildTabSpec(TAB_4G, R.string.data_usage_tab_4g));
268 }
269
270 if (mShowWifi) {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700271 if (!mobileSplit) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700272 mTabHost.addTab(buildTabSpec(TAB_MOBILE, R.string.data_usage_tab_mobile));
273 }
274 mTabHost.addTab(buildTabSpec(TAB_WIFI, R.string.data_usage_tab_wifi));
275 }
276
277 if (mTabWidget.getTabCount() > 0) {
278 // select first tab, which will kick off updateBody()
279 mTabHost.setCurrentTab(0);
280 } else {
281 // no tabs shown; update body manually
282 updateBody();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700283 }
284 }
285
Jeff Sharkey8a503642011-06-10 13:31:21 -0700286 /**
287 * Factory that provide empty {@link View} to make {@link TabHost} happy.
288 */
289 private TabContentFactory mEmptyTabContent = new TabContentFactory() {
290 /** {@inheritDoc} */
291 public View createTabContent(String tag) {
292 return new View(mTabHost.getContext());
293 }
294 };
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700295
Jeff Sharkey8a503642011-06-10 13:31:21 -0700296 /**
297 * Build {@link TabSpec} with thin indicator, and empty content.
298 */
299 private TabSpec buildTabSpec(String tag, int titleRes) {
300 final LayoutInflater inflater = LayoutInflater.from(mTabWidget.getContext());
301 final View indicator = inflater.inflate(
302 R.layout.tab_indicator_thin_holo, mTabWidget, false);
303 final TextView title = (TextView) indicator.findViewById(android.R.id.title);
304 title.setText(titleRes);
305 return mTabHost.newTabSpec(tag).setIndicator(indicator).setContent(mEmptyTabContent);
306 }
307
308 private OnTabChangeListener mTabListener = new OnTabChangeListener() {
309 /** {@inheritDoc} */
310 public void onTabChanged(String tabId) {
311 // user changed tab; update body
312 updateBody();
313 }
314 };
315
316 /**
317 * Update body content based on current tab. Loads
318 * {@link NetworkStatsHistory} and {@link NetworkPolicy} from system, and
319 * binds them to visible controls.
320 */
321 private void updateBody() {
322 final String tabTag = mTabHost.getCurrentTabTag();
323 final String currentTab = tabTag != null ? tabTag : TAB_MOBILE;
324
325 if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab);
326
327 if (TAB_WIFI.equals(currentTab)) {
328 // wifi doesn't have any controls
329 mDataEnabledView.setVisibility(View.GONE);
330 mDisableAtLimitView.setVisibility(View.GONE);
331 mTemplate = TEMPLATE_WIFI;
332
333 } else {
334 // make sure we show for non-wifi
335 mDataEnabledView.setVisibility(View.VISIBLE);
336 mDisableAtLimitView.setVisibility(View.VISIBLE);
337 }
338
339 if (TAB_MOBILE.equals(currentTab)) {
340 mDataEnabled.setTitle(R.string.data_usage_enable_mobile);
341 mDisableAtLimit.setTitle(R.string.data_usage_disable_mobile_limit);
342 mTemplate = TEMPLATE_MOBILE_ALL;
343
344 } else if (TAB_3G.equals(currentTab)) {
345 mDataEnabled.setTitle(R.string.data_usage_enable_3g);
346 mDisableAtLimit.setTitle(R.string.data_usage_disable_3g_limit);
347 mTemplate = TEMPLATE_MOBILE_3G_LOWER;
348
349 } else if (TAB_4G.equals(currentTab)) {
350 mDataEnabled.setTitle(R.string.data_usage_enable_4g);
351 mDisableAtLimit.setTitle(R.string.data_usage_disable_4g_limit);
352 mTemplate = TEMPLATE_MOBILE_4G;
353
354 }
355
356 // TODO: populate checkbox based on radio preferences
357 mDataEnabled.setChecked(true);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700358
359 try {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700360 // load stats for current template
Jeff Sharkey8a503642011-06-10 13:31:21 -0700361 mHistory = mStatsService.getHistoryForNetwork(mTemplate);
362 } catch (RemoteException e) {
363 // since we can't do much without policy or history, and we don't
364 // want to leave with half-baked UI, we bail hard.
365 throw new RuntimeException("problem reading network policy or stats", e);
366 }
367
Jeff Sharkey8a503642011-06-10 13:31:21 -0700368 // bind chart to historical stats
Jeff Sharkey8a503642011-06-10 13:31:21 -0700369 mChart.bindNetworkStats(mHistory);
370
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700371 updatePolicy(true);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700372
373 // force scroll to top of body
374 mListView.smoothScrollToPosition(0);
375
376 // kick preference views so they rebind from changes above
377 refreshPreferenceViews();
378 }
379
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700380 private void setPolicyCycleDay(int cycleDay) {
381 if (LOGD) Log.d(TAG, "setPolicyCycleDay()");
382 mPolicyModifier.setPolicyCycleDay(mTemplate, cycleDay);
383 updatePolicy(true);
384 }
385
386 private void setPolicyWarningBytes(long warningBytes) {
387 if (LOGD) Log.d(TAG, "setPolicyWarningBytes()");
388 mPolicyModifier.setPolicyWarningBytes(mTemplate, warningBytes);
389 updatePolicy(false);
390 }
391
392 private void setPolicyLimitBytes(long limitBytes) {
393 if (LOGD) Log.d(TAG, "setPolicyLimitBytes()");
394 mPolicyModifier.setPolicyLimitBytes(mTemplate, limitBytes);
395 updatePolicy(false);
396 }
397
Jeff Sharkey8a503642011-06-10 13:31:21 -0700398 /**
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700399 * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for
400 * current {@link #mTemplate}.
401 */
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700402 private void updatePolicy(boolean refreshCycle) {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700403 final NetworkPolicy policy = mPolicyModifier.getPolicy(mTemplate);
404
405 // reflect policy limit in checkbox
406 mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED);
407 mChart.bindNetworkPolicy(policy);
408
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700409 if (refreshCycle) {
410 // generate cycle list based on policy and available history
411 updateCycleList(policy);
412 }
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700413
414 // kick preference views so they rebind from changes above
415 refreshPreferenceViews();
416 }
417
418 /**
Jeff Sharkey8a503642011-06-10 13:31:21 -0700419 * Return full time bounds (earliest and latest time recorded) of the given
420 * {@link NetworkStatsHistory}.
421 */
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700422 public static long[] getHistoryBounds(NetworkStatsHistory history) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700423 final long currentTime = System.currentTimeMillis();
424
425 long start = currentTime;
426 long end = currentTime;
427 if (history.bucketCount > 0) {
428 start = history.bucketStart[0];
429 end = history.bucketStart[history.bucketCount - 1];
430 }
431
432 return new long[] { start, end };
433 }
434
435 /**
436 * Rebuild {@link #mCycleAdapter} based on {@link NetworkPolicy#cycleDay}
437 * and available {@link NetworkStatsHistory} data. Always selects the newest
438 * item, updating the inspection range on {@link #mChart}.
439 */
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700440 private void updateCycleList(NetworkPolicy policy) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700441 mCycleAdapter.clear();
442
443 final Context context = mCycleSpinner.getContext();
444
445 final long[] bounds = getHistoryBounds(mHistory);
446 final long historyStart = bounds[0];
447 final long historyEnd = bounds[1];
448
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700449 if (policy != null) {
450 // find the next cycle boundary
451 long cycleEnd = computeNextCycleBoundary(historyEnd, policy);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700452
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700453 int guardCount = 0;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700454
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700455 // walk backwards, generating all valid cycle ranges
456 while (cycleEnd > historyStart) {
457 final long cycleStart = computeLastCycleBoundary(cycleEnd, policy);
458 Log.d(TAG, "generating cs=" + cycleStart + " to ce=" + cycleEnd + " waiting for hs="
459 + historyStart);
460 mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd));
461 cycleEnd = cycleStart;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700462
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700463 // TODO: remove this guard once we have better testing
464 if (guardCount++ > 50) {
465 Log.wtf(TAG, "stuck generating ranges for bounds=" + Arrays.toString(bounds)
466 + " and policy=" + policy);
467 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700468 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700469
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700470 // one last cycle entry to modify policy cycle day
471 mCycleAdapter.add(new CycleChangeItem(context));
472
473 } else {
474 // no valid cycle; show all data
475 // TODO: offer simple ranges like "last week" etc
476 mCycleAdapter.add(new CycleItem(context, historyStart, historyEnd));
477
478 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700479
480 // force pick the current cycle (first item)
481 mCycleSpinner.setSelection(0);
482 mCycleListener.onItemSelected(mCycleSpinner, null, 0, 0);
483 }
484
485 /**
486 * Force rebind of hijacked {@link Preference} views.
487 */
488 private void refreshPreferenceViews() {
489 mDataEnabledView = mDataEnabled.getView(mDataEnabledView, mListView);
490 mDisableAtLimitView = mDisableAtLimit.getView(mDisableAtLimitView, mListView);
491 }
492
493 private OnClickListener mDataEnabledListener = new OnClickListener() {
494 /** {@inheritDoc} */
495 public void onClick(View v) {
496 mDataEnabled.setChecked(!mDataEnabled.isChecked());
497 refreshPreferenceViews();
498
499 // TODO: wire up to telephony to enable/disable radios
500 }
501 };
502
503 private OnClickListener mDisableAtLimitListener = new OnClickListener() {
504 /** {@inheritDoc} */
505 public void onClick(View v) {
506 final boolean disableAtLimit = !mDisableAtLimit.isChecked();
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700507 if (disableAtLimit) {
508 // enabling limit; show confirmation dialog which eventually
509 // calls setPolicyLimitBytes() once user confirms.
510 ConfirmLimitFragment.show(DataUsageSummary.this);
511 } else {
512 setPolicyLimitBytes(LIMIT_DISABLED);
513 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700514 }
515 };
516
517 private OnItemClickListener mListListener = new OnItemClickListener() {
518 /** {@inheritDoc} */
519 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700520 final AppUsageItem app = (AppUsageItem) parent.getItemAtPosition(position);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700521
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700522 final Bundle args = new Bundle();
523 args.putInt(Intent.EXTRA_UID, app.uid);
524
525 final PreferenceActivity activity = (PreferenceActivity) getActivity();
526 activity.startPreferencePanel(DataUsageAppDetail.class.getName(), args,
527 R.string.data_usage_summary_title, null, null, 0);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700528 }
529 };
530
531 private OnItemSelectedListener mCycleListener = new OnItemSelectedListener() {
532 /** {@inheritDoc} */
533 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
534 final CycleItem cycle = (CycleItem) parent.getItemAtPosition(position);
535 if (cycle instanceof CycleChangeItem) {
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700536 // show cycle editor; will eventually call setPolicyCycleDay()
537 // when user finishes editing.
538 CycleEditorFragment.show(DataUsageSummary.this);
539
540 // reset spinner to something other than "change cycle..."
541 mCycleSpinner.setSelection(0);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700542
543 } else {
Jeff Sharkey8e911d72011-06-14 22:41:21 -0700544 if (LOGD) {
545 Log.d(TAG, "showing cycle " + cycle + ", start=" + cycle.start + ", end="
546 + cycle.end + "]");
547 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700548
549 // update chart to show selected cycle, and update detail data
550 // to match updated sweep bounds.
551 final long[] bounds = getHistoryBounds(mHistory);
552 mChart.setVisibleRange(cycle.start, cycle.end, bounds[1]);
553
554 updateDetailData();
555 }
556 }
557
558 /** {@inheritDoc} */
559 public void onNothingSelected(AdapterView<?> parent) {
560 // ignored
561 }
562 };
563
564 /**
565 * Update {@link #mAdapter} with sorted list of applications data usage,
566 * based on current inspection from {@link #mChart}.
567 */
568 private void updateDetailData() {
569 if (LOGD) Log.d(TAG, "updateDetailData()");
570
571 try {
572 final long[] range = mChart.getInspectRange();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700573 final NetworkStats stats = mStatsService.getSummaryForAllUid(
Jeff Sharkey8a503642011-06-10 13:31:21 -0700574 range[0], range[1], mTemplate);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700575 mAdapter.bindStats(stats);
576 } catch (RemoteException e) {
577 Log.w(TAG, "problem reading stats");
578 }
579 }
580
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700581 private static String getActiveSubscriberId(Context context) {
582 final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
583 Context.TELEPHONY_SERVICE);
584 return telephony.getSubscriberId();
585 }
586
Jeff Sharkey8a503642011-06-10 13:31:21 -0700587 private DataUsageChartListener mChartListener = new DataUsageChartListener() {
588 /** {@inheritDoc} */
589 public void onInspectRangeChanged() {
590 if (LOGD) Log.d(TAG, "onInspectRangeChanged()");
591 updateDetailData();
592 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700593
Jeff Sharkey8a503642011-06-10 13:31:21 -0700594 /** {@inheritDoc} */
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700595 public void onWarningChanged() {
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700596 setPolicyWarningBytes(mChart.getWarningBytes());
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700597 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700598
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700599 /** {@inheritDoc} */
600 public void onLimitChanged() {
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700601 setPolicyLimitBytes(mChart.getLimitBytes());
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700602 }
603 };
604
605
606 /**
Jeff Sharkey8a503642011-06-10 13:31:21 -0700607 * List item that reflects a specific data usage cycle.
608 */
609 public static class CycleItem {
610 public CharSequence label;
611 public long start;
612 public long end;
613
614 private static final StringBuilder sBuilder = new StringBuilder(50);
615 private static final java.util.Formatter sFormatter = new java.util.Formatter(
616 sBuilder, Locale.getDefault());
617
618 CycleItem(CharSequence label) {
619 this.label = label;
620 }
621
622 public CycleItem(Context context, long start, long end) {
623 this.label = formatDateRangeUtc(context, start, end);
624 this.start = start;
625 this.end = end;
626 }
627
628 private static String formatDateRangeUtc(Context context, long start, long end) {
629 synchronized (sBuilder) {
630 sBuilder.setLength(0);
631 return DateUtils.formatDateRange(context, sFormatter, start, end,
632 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH,
633 Time.TIMEZONE_UTC).toString();
634 }
635 }
636
637 @Override
638 public String toString() {
639 return label.toString();
640 }
641 }
642
643 /**
644 * Special-case data usage cycle that triggers dialog to change
645 * {@link NetworkPolicy#cycleDay}.
646 */
647 public static class CycleChangeItem extends CycleItem {
648 public CycleChangeItem(Context context) {
649 super(context.getString(R.string.data_usage_change_cycle));
650 }
651 }
652
653 public static class CycleAdapter extends ArrayAdapter<CycleItem> {
654 public CycleAdapter(Context context) {
655 super(context, android.R.layout.simple_spinner_item);
656 setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
657 }
658 }
659
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700660 private static class AppUsageItem implements Comparable<AppUsageItem> {
661 public int uid;
662 public long total;
663
664 /** {@inheritDoc} */
665 public int compareTo(AppUsageItem another) {
666 return Long.compare(another.total, total);
667 }
668 }
669
Jeff Sharkey8a503642011-06-10 13:31:21 -0700670 /**
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700671 * Adapter of applications, sorted by total usage descending.
672 */
673 public static class DataUsageAdapter extends BaseAdapter {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700674 private ArrayList<AppUsageItem> mItems = Lists.newArrayList();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700675
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700676 public void bindStats(NetworkStats stats) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700677 mItems.clear();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700678
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700679 for (int i = 0; i < stats.size; i++) {
Jeff Sharkey8e911d72011-06-14 22:41:21 -0700680 final long total = stats.rx[i] + stats.tx[i];
681 if (total > 0) {
682 final AppUsageItem item = new AppUsageItem();
683 item.uid = stats.uid[i];
684 item.total = total;
685 mItems.add(item);
686 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700687 }
688
Jeff Sharkey8a503642011-06-10 13:31:21 -0700689 Collections.sort(mItems);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700690 notifyDataSetChanged();
691 }
692
693 @Override
694 public int getCount() {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700695 return mItems.size();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700696 }
697
698 @Override
699 public Object getItem(int position) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700700 return mItems.get(position);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700701 }
702
703 @Override
704 public long getItemId(int position) {
705 return position;
706 }
707
708 @Override
709 public View getView(int position, View convertView, ViewGroup parent) {
710 if (convertView == null) {
711 convertView = LayoutInflater.from(parent.getContext()).inflate(
712 android.R.layout.simple_list_item_2, parent, false);
713 }
714
715 final Context context = parent.getContext();
716 final PackageManager pm = context.getPackageManager();
717
718 final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
719 final TextView text2 = (TextView) convertView.findViewById(android.R.id.text2);
720
Jeff Sharkey8a503642011-06-10 13:31:21 -0700721 final AppUsageItem item = mItems.get(position);
Jeff Sharkey8e911d72011-06-14 22:41:21 -0700722 text1.setText(resolveLabelForUid(pm, item.uid));
Jeff Sharkey8a503642011-06-10 13:31:21 -0700723 text2.setText(Formatter.formatFileSize(context, item.total));
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700724
725 return convertView;
726 }
727
728 }
729
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700730 /**
731 * Dialog to request user confirmation before setting
732 * {@link NetworkPolicy#limitBytes}.
733 */
734 public static class ConfirmLimitFragment extends DialogFragment {
735 public static final String EXTRA_MESSAGE_ID = "messageId";
736 public static final String EXTRA_LIMIT_BYTES = "limitBytes";
737
738 public static void show(DataUsageSummary parent) {
739 final Bundle args = new Bundle();
740
741 // TODO: customize default limits based on network template
742 switch (parent.mTemplate) {
743 case TEMPLATE_MOBILE_3G_LOWER: {
744 args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_3g);
745 args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES);
746 break;
747 }
748 case TEMPLATE_MOBILE_4G: {
749 args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_4g);
750 args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES);
751 break;
752 }
753 case TEMPLATE_MOBILE_ALL: {
754 args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_mobile);
755 args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES);
756 break;
757 }
758 }
759
760 final ConfirmLimitFragment dialog = new ConfirmLimitFragment();
761 dialog.setArguments(args);
762 dialog.setTargetFragment(parent, 0);
763 dialog.show(parent.getFragmentManager(), TAG_CONFIRM_LIMIT);
764 }
765
766 @Override
767 public Dialog onCreateDialog(Bundle savedInstanceState) {
768 final Context context = getActivity();
769
770 final int messageId = getArguments().getInt(EXTRA_MESSAGE_ID);
771 final long limitBytes = getArguments().getLong(EXTRA_LIMIT_BYTES);
772
773 final AlertDialog.Builder builder = new AlertDialog.Builder(context);
774 builder.setTitle(R.string.data_usage_limit_dialog_title);
775 builder.setMessage(messageId);
776
777 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
778 public void onClick(DialogInterface dialog, int which) {
779 final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
780 if (target != null) {
781 target.setPolicyLimitBytes(limitBytes);
782 }
783 }
784 });
785
786 return builder.create();
787 }
788 }
789
790 /**
791 * Dialog to edit {@link NetworkPolicy#cycleDay}.
792 */
793 public static class CycleEditorFragment extends DialogFragment {
794 public static final String EXTRA_CYCLE_DAY = "cycleDay";
795
796 public static void show(DataUsageSummary parent) {
797 final NetworkPolicy policy = parent.mPolicyModifier.getPolicy(parent.mTemplate);
798 final Bundle args = new Bundle();
799 args.putInt(CycleEditorFragment.EXTRA_CYCLE_DAY, policy.cycleDay);
800
801 final CycleEditorFragment dialog = new CycleEditorFragment();
802 dialog.setArguments(args);
803 dialog.setTargetFragment(parent, 0);
804 dialog.show(parent.getFragmentManager(), TAG_CYCLE_EDITOR);
805 }
806
807 @Override
808 public Dialog onCreateDialog(Bundle savedInstanceState) {
809 final Context context = getActivity();
810
811 final AlertDialog.Builder builder = new AlertDialog.Builder(context);
812 final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
813
814 final View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false);
815 final NumberPicker cycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day);
816
817 final int oldCycleDay = getArguments().getInt(EXTRA_CYCLE_DAY, 1);
818
819 cycleDayPicker.setMinValue(1);
820 cycleDayPicker.setMaxValue(31);
821 cycleDayPicker.setValue(oldCycleDay);
822 cycleDayPicker.setWrapSelectorWheel(true);
823
824 builder.setTitle(R.string.data_usage_cycle_editor_title);
825 builder.setView(view);
826
827 builder.setPositiveButton(R.string.data_usage_cycle_editor_positive,
828 new DialogInterface.OnClickListener() {
829 public void onClick(DialogInterface dialog, int which) {
830 final int cycleDay = cycleDayPicker.getValue();
831 final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
832 if (target != null) {
833 target.setPolicyCycleDay(cycleDay);
834 }
835 }
836 });
837
838 return builder.create();
839 }
840 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700841
Jeff Sharkey8e911d72011-06-14 22:41:21 -0700842 /**
843 * Resolve best descriptive label for the given UID.
844 */
845 public static CharSequence resolveLabelForUid(PackageManager pm, int uid) {
846 final String[] packageNames = pm.getPackagesForUid(uid);
847 final int length = packageNames != null ? packageNames.length : 0;
848
849 CharSequence label = pm.getNameForUid(uid);
850 try {
851 if (length == 1) {
852 final ApplicationInfo info = pm.getApplicationInfo(packageNames[0], 0);
853 label = info.loadLabel(pm);
854 } else if (length > 1) {
855 for (String packageName : packageNames) {
856 final PackageInfo info = pm.getPackageInfo(packageName, 0);
857 if (info.sharedUserLabel != 0) {
858 label = pm.getText(packageName, info.sharedUserLabel, info.applicationInfo);
859 if (!TextUtils.isEmpty(label)) {
860 break;
861 }
862 }
863 }
864 }
865 } catch (NameNotFoundException e) {
866 }
867
868 if (TextUtils.isEmpty(label)) {
869 label = Integer.toString(uid);
870 }
871 return label;
872 }
873
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700874}