• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.settings;
18 
19 import static android.net.ConnectivityManager.TYPE_ETHERNET;
20 import static android.net.ConnectivityManager.TYPE_MOBILE;
21 import static android.net.ConnectivityManager.TYPE_WIMAX;
22 import static android.net.NetworkPolicy.LIMIT_DISABLED;
23 import static android.net.NetworkPolicy.WARNING_DISABLED;
24 import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
25 import static android.net.NetworkPolicyManager.POLICY_NONE;
26 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
27 import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
28 import static android.net.NetworkPolicyManager.computeNextCycleBoundary;
29 import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
30 import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
31 import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
32 import static android.net.NetworkTemplate.MATCH_WIFI;
33 import static android.net.NetworkTemplate.buildTemplateEthernet;
34 import static android.net.NetworkTemplate.buildTemplateMobile3gLower;
35 import static android.net.NetworkTemplate.buildTemplateMobile4g;
36 import static android.net.NetworkTemplate.buildTemplateMobileAll;
37 import static android.net.NetworkTemplate.buildTemplateWifi;
38 import static android.net.TrafficStats.UID_REMOVED;
39 import static android.net.TrafficStats.UID_TETHERING;
40 import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
41 import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
42 import static android.text.format.Time.TIMEZONE_UTC;
43 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
44 import static com.android.internal.util.Preconditions.checkNotNull;
45 import static com.android.settings.Utils.prepareCustomPreferencesList;
46 
47 import android.animation.LayoutTransition;
48 import android.app.AlertDialog;
49 import android.app.Dialog;
50 import android.app.DialogFragment;
51 import android.app.Fragment;
52 import android.app.FragmentTransaction;
53 import android.app.LoaderManager.LoaderCallbacks;
54 import android.content.ContentResolver;
55 import android.content.Context;
56 import android.content.DialogInterface;
57 import android.content.Intent;
58 import android.content.Loader;
59 import android.content.SharedPreferences;
60 import android.content.pm.PackageManager;
61 import android.content.res.Resources;
62 import android.graphics.Color;
63 import android.graphics.drawable.ColorDrawable;
64 import android.graphics.drawable.Drawable;
65 import android.net.ConnectivityManager;
66 import android.net.INetworkPolicyManager;
67 import android.net.INetworkStatsService;
68 import android.net.NetworkPolicy;
69 import android.net.NetworkPolicyManager;
70 import android.net.NetworkStats;
71 import android.net.NetworkStatsHistory;
72 import android.net.NetworkTemplate;
73 import android.os.AsyncTask;
74 import android.os.Bundle;
75 import android.os.INetworkManagementService;
76 import android.os.RemoteException;
77 import android.os.ServiceManager;
78 import android.os.SystemProperties;
79 import android.preference.Preference;
80 import android.provider.Settings;
81 import android.telephony.TelephonyManager;
82 import android.text.TextUtils;
83 import android.text.format.DateUtils;
84 import android.text.format.Formatter;
85 import android.util.Log;
86 import android.util.SparseArray;
87 import android.view.LayoutInflater;
88 import android.view.Menu;
89 import android.view.MenuInflater;
90 import android.view.MenuItem;
91 import android.view.View;
92 import android.view.View.OnClickListener;
93 import android.view.ViewGroup;
94 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
95 import android.widget.AdapterView;
96 import android.widget.AdapterView.OnItemClickListener;
97 import android.widget.AdapterView.OnItemSelectedListener;
98 import android.widget.ArrayAdapter;
99 import android.widget.BaseAdapter;
100 import android.widget.Button;
101 import android.widget.CheckBox;
102 import android.widget.CompoundButton;
103 import android.widget.CompoundButton.OnCheckedChangeListener;
104 import android.widget.ImageView;
105 import android.widget.LinearLayout;
106 import android.widget.ListView;
107 import android.widget.NumberPicker;
108 import android.widget.ProgressBar;
109 import android.widget.Spinner;
110 import android.widget.Switch;
111 import android.widget.TabHost;
112 import android.widget.TabHost.OnTabChangeListener;
113 import android.widget.TabHost.TabContentFactory;
114 import android.widget.TabHost.TabSpec;
115 import android.widget.TabWidget;
116 import android.widget.TextView;
117 
118 import com.android.internal.telephony.Phone;
119 import com.android.settings.drawable.InsetBoundsDrawable;
120 import com.android.settings.net.ChartData;
121 import com.android.settings.net.ChartDataLoader;
122 import com.android.settings.net.NetworkPolicyEditor;
123 import com.android.settings.net.SummaryForAllUidLoader;
124 import com.android.settings.net.UidDetail;
125 import com.android.settings.net.UidDetailProvider;
126 import com.android.settings.widget.ChartDataUsageView;
127 import com.android.settings.widget.ChartDataUsageView.DataUsageChartListener;
128 import com.android.settings.widget.PieChartView;
129 import com.google.android.collect.Lists;
130 
131 import java.util.ArrayList;
132 import java.util.Arrays;
133 import java.util.Collections;
134 import java.util.List;
135 import java.util.Locale;
136 
137 import libcore.util.Objects;
138 
139 /**
140  * Panel show data usage history across various networks, including options to
141  * inspect based on usage cycle and control through {@link NetworkPolicy}.
142  */
143 public class DataUsageSummary extends Fragment {
144     private static final String TAG = "DataUsage";
145     private static final boolean LOGD = true;
146 
147     // TODO: remove this testing code
148     private static final boolean TEST_ANIM = false;
149     private static final boolean TEST_RADIOS = false;
150     private static final String TEST_RADIOS_PROP = "test.radios";
151 
152     private static final String TAB_3G = "3g";
153     private static final String TAB_4G = "4g";
154     private static final String TAB_MOBILE = "mobile";
155     private static final String TAB_WIFI = "wifi";
156     private static final String TAB_ETHERNET = "ethernet";
157 
158     private static final String TAG_CONFIRM_DATA_DISABLE = "confirmDataDisable";
159     private static final String TAG_CONFIRM_DATA_ROAMING = "confirmDataRoaming";
160     private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
161     private static final String TAG_CYCLE_EDITOR = "cycleEditor";
162     private static final String TAG_WARNING_EDITOR = "warningEditor";
163     private static final String TAG_LIMIT_EDITOR = "limitEditor";
164     private static final String TAG_CONFIRM_RESTRICT = "confirmRestrict";
165     private static final String TAG_DENIED_RESTRICT = "deniedRestrict";
166     private static final String TAG_CONFIRM_APP_RESTRICT = "confirmAppRestrict";
167     private static final String TAG_APP_DETAILS = "appDetails";
168 
169     private static final int LOADER_CHART_DATA = 2;
170     private static final int LOADER_SUMMARY = 3;
171 
172     private static final long KB_IN_BYTES = 1024;
173     private static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
174     private static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
175 
176     private INetworkManagementService mNetworkService;
177     private INetworkStatsService mStatsService;
178     private INetworkPolicyManager mPolicyService;
179     private ConnectivityManager mConnService;
180 
181     private static final String PREF_FILE = "data_usage";
182     private static final String PREF_SHOW_WIFI = "show_wifi";
183     private static final String PREF_SHOW_ETHERNET = "show_ethernet";
184 
185     private SharedPreferences mPrefs;
186 
187     private TabHost mTabHost;
188     private ViewGroup mTabsContainer;
189     private TabWidget mTabWidget;
190     private ListView mListView;
191     private DataUsageAdapter mAdapter;
192 
193     /** Distance to inset content from sides, when needed. */
194     private int mInsetSide = 0;
195 
196     private ViewGroup mHeader;
197 
198     private ViewGroup mNetworkSwitchesContainer;
199     private LinearLayout mNetworkSwitches;
200     private Switch mDataEnabled;
201     private View mDataEnabledView;
202     private CheckBox mDisableAtLimit;
203     private View mDisableAtLimitView;
204 
205     private View mCycleView;
206     private Spinner mCycleSpinner;
207     private CycleAdapter mCycleAdapter;
208 
209     private ChartDataUsageView mChart;
210     private TextView mUsageSummary;
211     private TextView mEmpty;
212 
213     private View mAppDetail;
214     private ImageView mAppIcon;
215     private ViewGroup mAppTitles;
216     private PieChartView mAppPieChart;
217     private TextView mAppForeground;
218     private TextView mAppBackground;
219     private Button mAppSettings;
220 
221     private LinearLayout mAppSwitches;
222     private CheckBox mAppRestrict;
223     private View mAppRestrictView;
224 
225     private boolean mShowWifi = false;
226     private boolean mShowEthernet = false;
227 
228     private NetworkTemplate mTemplate;
229     private ChartData mChartData;
230 
231     private int[] mAppDetailUids = null;
232 
233     private Intent mAppSettingsIntent;
234 
235     private NetworkPolicyEditor mPolicyEditor;
236 
237     private String mCurrentTab = null;
238     private String mIntentTab = null;
239 
240     private MenuItem mMenuDataRoaming;
241     private MenuItem mMenuRestrictBackground;
242 
243     /** Flag used to ignore listeners during binding. */
244     private boolean mBinding;
245 
246     private UidDetailProvider mUidDetailProvider;
247 
248     @Override
onCreate(Bundle savedInstanceState)249     public void onCreate(Bundle savedInstanceState) {
250         super.onCreate(savedInstanceState);
251 
252         mNetworkService = INetworkManagementService.Stub.asInterface(
253                 ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
254         mStatsService = INetworkStatsService.Stub.asInterface(
255                 ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
256         mPolicyService = INetworkPolicyManager.Stub.asInterface(
257                 ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
258         mConnService = (ConnectivityManager) getActivity().getSystemService(
259                 Context.CONNECTIVITY_SERVICE);
260 
261         mPrefs = getActivity().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
262 
263         mPolicyEditor = new NetworkPolicyEditor(mPolicyService);
264         mPolicyEditor.read();
265 
266         mShowWifi = mPrefs.getBoolean(PREF_SHOW_WIFI, false);
267         mShowEthernet = mPrefs.getBoolean(PREF_SHOW_ETHERNET, false);
268 
269         setHasOptionsMenu(true);
270     }
271 
272     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)273     public View onCreateView(LayoutInflater inflater, ViewGroup container,
274             Bundle savedInstanceState) {
275 
276         final Context context = inflater.getContext();
277         final View view = inflater.inflate(R.layout.data_usage_summary, container, false);
278 
279         mUidDetailProvider = new UidDetailProvider(context);
280 
281         mTabHost = (TabHost) view.findViewById(android.R.id.tabhost);
282         mTabsContainer = (ViewGroup) view.findViewById(R.id.tabs_container);
283         mTabWidget = (TabWidget) view.findViewById(android.R.id.tabs);
284         mListView = (ListView) view.findViewById(android.R.id.list);
285 
286         // decide if we need to manually inset our content, or if we should rely
287         // on parent container for inset.
288         final boolean shouldInset = mListView.getScrollBarStyle()
289                 == View.SCROLLBARS_OUTSIDE_OVERLAY;
290         if (shouldInset) {
291             mInsetSide = view.getResources().getDimensionPixelOffset(
292                     com.android.internal.R.dimen.preference_fragment_padding_side);
293         } else {
294             mInsetSide = 0;
295         }
296 
297         // adjust padding around tabwidget as needed
298         prepareCustomPreferencesList(container, view, mListView, true);
299 
300         mTabHost.setup();
301         mTabHost.setOnTabChangedListener(mTabListener);
302 
303         mHeader = (ViewGroup) inflater.inflate(R.layout.data_usage_header, mListView, false);
304         mHeader.setClickable(true);
305 
306         mListView.addHeaderView(mHeader, null, true);
307         mListView.setItemsCanFocus(true);
308 
309         if (mInsetSide > 0) {
310             // inset selector and divider drawables
311             insetListViewDrawables(mListView, mInsetSide);
312             mHeader.setPadding(mInsetSide, 0, mInsetSide, 0);
313         }
314 
315         {
316             // bind network switches
317             mNetworkSwitchesContainer = (ViewGroup) mHeader.findViewById(
318                     R.id.network_switches_container);
319             mNetworkSwitches = (LinearLayout) mHeader.findViewById(R.id.network_switches);
320 
321             mDataEnabled = new Switch(inflater.getContext());
322             mDataEnabledView = inflatePreference(inflater, mNetworkSwitches, mDataEnabled);
323             mDataEnabled.setOnCheckedChangeListener(mDataEnabledListener);
324             mNetworkSwitches.addView(mDataEnabledView);
325 
326             mDisableAtLimit = new CheckBox(inflater.getContext());
327             mDisableAtLimit.setClickable(false);
328             mDisableAtLimit.setFocusable(false);
329             mDisableAtLimitView = inflatePreference(inflater, mNetworkSwitches, mDisableAtLimit);
330             mDisableAtLimitView.setClickable(true);
331             mDisableAtLimitView.setFocusable(true);
332             mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener);
333             mNetworkSwitches.addView(mDisableAtLimitView);
334         }
335 
336         // bind cycle dropdown
337         mCycleView = mHeader.findViewById(R.id.cycles);
338         mCycleSpinner = (Spinner) mCycleView.findViewById(R.id.cycles_spinner);
339         mCycleAdapter = new CycleAdapter(context);
340         mCycleSpinner.setAdapter(mCycleAdapter);
341         mCycleSpinner.setOnItemSelectedListener(mCycleListener);
342 
343         mChart = (ChartDataUsageView) mHeader.findViewById(R.id.chart);
344         mChart.setListener(mChartListener);
345 
346         {
347             // bind app detail controls
348             mAppDetail = mHeader.findViewById(R.id.app_detail);
349             mAppIcon = (ImageView) mAppDetail.findViewById(R.id.app_icon);
350             mAppTitles = (ViewGroup) mAppDetail.findViewById(R.id.app_titles);
351             mAppPieChart = (PieChartView) mAppDetail.findViewById(R.id.app_pie_chart);
352             mAppForeground = (TextView) mAppDetail.findViewById(R.id.app_foreground);
353             mAppBackground = (TextView) mAppDetail.findViewById(R.id.app_background);
354             mAppSwitches = (LinearLayout) mAppDetail.findViewById(R.id.app_switches);
355 
356             mAppSettings = (Button) mAppDetail.findViewById(R.id.app_settings);
357             mAppSettings.setOnClickListener(mAppSettingsListener);
358 
359             mAppRestrict = new CheckBox(inflater.getContext());
360             mAppRestrict.setClickable(false);
361             mAppRestrict.setFocusable(false);
362             mAppRestrictView = inflatePreference(inflater, mAppSwitches, mAppRestrict);
363             mAppRestrictView.setClickable(true);
364             mAppRestrictView.setFocusable(true);
365             mAppRestrictView.setOnClickListener(mAppRestrictListener);
366             mAppSwitches.addView(mAppRestrictView);
367         }
368 
369         mUsageSummary = (TextView) mHeader.findViewById(R.id.usage_summary);
370         mEmpty = (TextView) mHeader.findViewById(android.R.id.empty);
371 
372         // only assign layout transitions once first layout is finished
373         mListView.getViewTreeObserver().addOnGlobalLayoutListener(mFirstLayoutListener);
374 
375         mAdapter = new DataUsageAdapter(mUidDetailProvider, mInsetSide);
376         mListView.setOnItemClickListener(mListListener);
377         mListView.setAdapter(mAdapter);
378 
379         return view;
380     }
381 
382     @Override
onResume()383     public void onResume() {
384         super.onResume();
385 
386         // pick default tab based on incoming intent
387         final Intent intent = getActivity().getIntent();
388         mIntentTab = computeTabFromIntent(intent);
389 
390         // this kicks off chain reaction which creates tabs, binds the body to
391         // selected network, and binds chart, cycles and detail list.
392         updateTabs();
393 
394         // kick off background task to update stats
395         new AsyncTask<Void, Void, Void>() {
396             @Override
397             protected Void doInBackground(Void... params) {
398                 try {
399                     // wait a few seconds before kicking off
400                     Thread.sleep(2 * DateUtils.SECOND_IN_MILLIS);
401                     mStatsService.forceUpdate();
402                 } catch (InterruptedException e) {
403                 } catch (RemoteException e) {
404                 }
405                 return null;
406             }
407 
408             @Override
409             protected void onPostExecute(Void result) {
410                 if (isAdded()) {
411                     updateBody();
412                 }
413             }
414         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
415     }
416 
417     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)418     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
419         inflater.inflate(R.menu.data_usage, menu);
420     }
421 
422     @Override
onPrepareOptionsMenu(Menu menu)423     public void onPrepareOptionsMenu(Menu menu) {
424         final Context context = getActivity();
425         final boolean appDetailMode = isAppDetailMode();
426 
427         mMenuDataRoaming = menu.findItem(R.id.data_usage_menu_roaming);
428         mMenuDataRoaming.setVisible(hasMobileRadio(context) && !appDetailMode);
429         mMenuDataRoaming.setChecked(getDataRoaming());
430 
431         mMenuRestrictBackground = menu.findItem(R.id.data_usage_menu_restrict_background);
432         mMenuRestrictBackground.setVisible(!appDetailMode);
433         mMenuRestrictBackground.setChecked(getRestrictBackground());
434 
435         final MenuItem split4g = menu.findItem(R.id.data_usage_menu_split_4g);
436         split4g.setVisible(hasMobile4gRadio(context) && !appDetailMode);
437         split4g.setChecked(isMobilePolicySplit());
438 
439         final MenuItem showWifi = menu.findItem(R.id.data_usage_menu_show_wifi);
440         if (hasWifiRadio(context) && hasMobileRadio(context)) {
441             showWifi.setVisible(!appDetailMode);
442             showWifi.setChecked(mShowWifi);
443         } else {
444             showWifi.setVisible(false);
445             mShowWifi = true;
446         }
447 
448         final MenuItem showEthernet = menu.findItem(R.id.data_usage_menu_show_ethernet);
449         if (hasEthernet(context) && hasMobileRadio(context)) {
450             showEthernet.setVisible(!appDetailMode);
451             showEthernet.setChecked(mShowEthernet);
452         } else {
453             showEthernet.setVisible(false);
454             mShowEthernet = true;
455         }
456     }
457 
458     @Override
onOptionsItemSelected(MenuItem item)459     public boolean onOptionsItemSelected(MenuItem item) {
460         switch (item.getItemId()) {
461             case R.id.data_usage_menu_roaming: {
462                 final boolean dataRoaming = !item.isChecked();
463                 if (dataRoaming) {
464                     ConfirmDataRoamingFragment.show(this);
465                 } else {
466                     // no confirmation to disable roaming
467                     setDataRoaming(false);
468                 }
469                 return true;
470             }
471             case R.id.data_usage_menu_restrict_background: {
472                 final boolean restrictBackground = !item.isChecked();
473                 if (restrictBackground) {
474                     if (hasLimitedNetworks()) {
475                         ConfirmRestrictFragment.show(this);
476                     } else {
477                         DeniedRestrictFragment.show(this);
478                     }
479                 } else {
480                     // no confirmation to drop restriction
481                     setRestrictBackground(false);
482                 }
483                 return true;
484             }
485             case R.id.data_usage_menu_split_4g: {
486                 final boolean mobileSplit = !item.isChecked();
487                 setMobilePolicySplit(mobileSplit);
488                 item.setChecked(isMobilePolicySplit());
489                 updateTabs();
490                 return true;
491             }
492             case R.id.data_usage_menu_show_wifi: {
493                 mShowWifi = !item.isChecked();
494                 mPrefs.edit().putBoolean(PREF_SHOW_WIFI, mShowWifi).apply();
495                 item.setChecked(mShowWifi);
496                 updateTabs();
497                 return true;
498             }
499             case R.id.data_usage_menu_show_ethernet: {
500                 mShowEthernet = !item.isChecked();
501                 mPrefs.edit().putBoolean(PREF_SHOW_ETHERNET, mShowEthernet).apply();
502                 item.setChecked(mShowEthernet);
503                 updateTabs();
504                 return true;
505             }
506         }
507         return false;
508     }
509 
510     @Override
onDestroyView()511     public void onDestroyView() {
512         super.onDestroyView();
513 
514         mDataEnabledView = null;
515         mDisableAtLimitView = null;
516 
517         mUidDetailProvider.clearCache();
518         mUidDetailProvider = null;
519     }
520 
521     /**
522      * Listener to setup {@link LayoutTransition} after first layout pass.
523      */
524     private OnGlobalLayoutListener mFirstLayoutListener = new OnGlobalLayoutListener() {
525         /** {@inheritDoc} */
526         public void onGlobalLayout() {
527             mListView.getViewTreeObserver().removeGlobalOnLayoutListener(mFirstLayoutListener);
528 
529             mTabsContainer.setLayoutTransition(buildLayoutTransition());
530             mHeader.setLayoutTransition(buildLayoutTransition());
531             mNetworkSwitchesContainer.setLayoutTransition(buildLayoutTransition());
532 
533             final LayoutTransition chartTransition = buildLayoutTransition();
534             chartTransition.setStartDelay(LayoutTransition.APPEARING, 0);
535             chartTransition.setStartDelay(LayoutTransition.DISAPPEARING, 0);
536             chartTransition.setAnimator(LayoutTransition.APPEARING, null);
537             chartTransition.setAnimator(LayoutTransition.DISAPPEARING, null);
538             mChart.setLayoutTransition(chartTransition);
539         }
540     };
541 
buildLayoutTransition()542     private static LayoutTransition buildLayoutTransition() {
543         final LayoutTransition transition = new LayoutTransition();
544         if (TEST_ANIM) {
545             transition.setDuration(1500);
546         }
547         transition.setAnimateParentHierarchy(false);
548         return transition;
549     }
550 
551     /**
552      * Rebuild all tabs based on {@link NetworkPolicyEditor} and
553      * {@link #mShowWifi}, hiding the tabs entirely when applicable. Selects
554      * first tab, and kicks off a full rebind of body contents.
555      */
updateTabs()556     private void updateTabs() {
557         final Context context = getActivity();
558         mTabHost.clearAllTabs();
559 
560         final boolean mobileSplit = isMobilePolicySplit();
561         if (mobileSplit && hasMobile4gRadio(context)) {
562             mTabHost.addTab(buildTabSpec(TAB_3G, R.string.data_usage_tab_3g));
563             mTabHost.addTab(buildTabSpec(TAB_4G, R.string.data_usage_tab_4g));
564         } else if (hasMobileRadio(context)) {
565             mTabHost.addTab(buildTabSpec(TAB_MOBILE, R.string.data_usage_tab_mobile));
566         }
567         if (mShowWifi && hasWifiRadio(context)) {
568             mTabHost.addTab(buildTabSpec(TAB_WIFI, R.string.data_usage_tab_wifi));
569         }
570         if (mShowEthernet && hasEthernet(context)) {
571             mTabHost.addTab(buildTabSpec(TAB_ETHERNET, R.string.data_usage_tab_ethernet));
572         }
573 
574         final boolean multipleTabs = mTabWidget.getTabCount() > 1;
575         mTabWidget.setVisibility(multipleTabs ? View.VISIBLE : View.GONE);
576         if (mIntentTab != null) {
577             if (Objects.equal(mIntentTab, mTabHost.getCurrentTabTag())) {
578                 // already hit updateBody() when added; ignore
579                 updateBody();
580             } else {
581                 mTabHost.setCurrentTabByTag(mIntentTab);
582             }
583             mIntentTab = null;
584         } else {
585             // already hit updateBody() when added; ignore
586         }
587     }
588 
589     /**
590      * Factory that provide empty {@link View} to make {@link TabHost} happy.
591      */
592     private TabContentFactory mEmptyTabContent = new TabContentFactory() {
593         /** {@inheritDoc} */
594         public View createTabContent(String tag) {
595             return new View(mTabHost.getContext());
596         }
597     };
598 
599     /**
600      * Build {@link TabSpec} with thin indicator, and empty content.
601      */
buildTabSpec(String tag, int titleRes)602     private TabSpec buildTabSpec(String tag, int titleRes) {
603         return mTabHost.newTabSpec(tag).setIndicator(getText(titleRes)).setContent(
604                 mEmptyTabContent);
605     }
606 
607     private OnTabChangeListener mTabListener = new OnTabChangeListener() {
608         /** {@inheritDoc} */
609         public void onTabChanged(String tabId) {
610             // user changed tab; update body
611             updateBody();
612         }
613     };
614 
615     /**
616      * Update body content based on current tab. Loads
617      * {@link NetworkStatsHistory} and {@link NetworkPolicy} from system, and
618      * binds them to visible controls.
619      */
updateBody()620     private void updateBody() {
621         mBinding = true;
622         if (!isAdded()) return;
623 
624         final Context context = getActivity();
625         final String currentTab = mTabHost.getCurrentTabTag();
626 
627         if (currentTab == null) {
628             Log.w(TAG, "no tab selected; hiding body");
629             mListView.setVisibility(View.GONE);
630             return;
631         } else {
632             mListView.setVisibility(View.VISIBLE);
633         }
634 
635         final boolean tabChanged = !currentTab.equals(mCurrentTab);
636         mCurrentTab = currentTab;
637 
638         if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab);
639 
640         mDataEnabledView.setVisibility(View.VISIBLE);
641 
642         if (TAB_MOBILE.equals(currentTab)) {
643             setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_mobile);
644             setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_mobile_limit);
645             mTemplate = buildTemplateMobileAll(getActiveSubscriberId(context));
646 
647         } else if (TAB_3G.equals(currentTab)) {
648             setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_3g);
649             setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_3g_limit);
650             // TODO: bind mDataEnabled to 3G radio state
651             mTemplate = buildTemplateMobile3gLower(getActiveSubscriberId(context));
652 
653         } else if (TAB_4G.equals(currentTab)) {
654             setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_4g);
655             setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_4g_limit);
656             // TODO: bind mDataEnabled to 4G radio state
657             mTemplate = buildTemplateMobile4g(getActiveSubscriberId(context));
658 
659         } else if (TAB_WIFI.equals(currentTab)) {
660             // wifi doesn't have any controls
661             mDataEnabledView.setVisibility(View.GONE);
662             mDisableAtLimitView.setVisibility(View.GONE);
663             mTemplate = buildTemplateWifi();
664 
665         } else if (TAB_ETHERNET.equals(currentTab)) {
666             // ethernet doesn't have any controls
667             mDataEnabledView.setVisibility(View.GONE);
668             mDisableAtLimitView.setVisibility(View.GONE);
669             mTemplate = buildTemplateEthernet();
670 
671         } else {
672             throw new IllegalStateException("unknown tab: " + currentTab);
673         }
674 
675         // kick off loader for network history
676         // TODO: consider chaining two loaders together instead of reloading
677         // network history when showing app detail.
678         getLoaderManager().restartLoader(LOADER_CHART_DATA,
679                 ChartDataLoader.buildArgs(mTemplate, mAppDetailUids), mChartDataCallbacks);
680 
681         // detail mode can change visible menus, invalidate
682         getActivity().invalidateOptionsMenu();
683 
684         mBinding = false;
685     }
686 
isAppDetailMode()687     private boolean isAppDetailMode() {
688         return mAppDetailUids != null;
689     }
690 
getAppDetailPrimaryUid()691     private int getAppDetailPrimaryUid() {
692         return mAppDetailUids[0];
693     }
694 
695     /**
696      * Update UID details panels to match {@link #mAppDetailUids}, showing or
697      * hiding them depending on {@link #isAppDetailMode()}.
698      */
updateAppDetail()699     private void updateAppDetail() {
700         final Context context = getActivity();
701         final PackageManager pm = context.getPackageManager();
702         final LayoutInflater inflater = getActivity().getLayoutInflater();
703 
704         if (isAppDetailMode()) {
705             mAppDetail.setVisibility(View.VISIBLE);
706             mCycleAdapter.setChangeVisible(false);
707         } else {
708             mAppDetail.setVisibility(View.GONE);
709             mCycleAdapter.setChangeVisible(true);
710 
711             // hide detail stats when not in detail mode
712             mChart.bindDetailNetworkStats(null);
713             return;
714         }
715 
716         // remove warning/limit sweeps while in detail mode
717         mChart.bindNetworkPolicy(null);
718 
719         // show icon and all labels appearing under this app
720         final int primaryUid = getAppDetailPrimaryUid();
721         final UidDetail detail = mUidDetailProvider.getUidDetail(primaryUid, true);
722         mAppIcon.setImageDrawable(detail.icon);
723 
724         mAppTitles.removeAllViews();
725         if (detail.detailLabels != null) {
726             for (CharSequence label : detail.detailLabels) {
727                 mAppTitles.addView(inflateAppTitle(inflater, mAppTitles, label));
728             }
729         } else {
730             mAppTitles.addView(inflateAppTitle(inflater, mAppTitles, detail.label));
731         }
732 
733         // enable settings button when package provides it
734         // TODO: target torwards entire UID instead of just first package
735         final String[] packageNames = pm.getPackagesForUid(primaryUid);
736         if (packageNames != null && packageNames.length > 0) {
737             mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE);
738             mAppSettingsIntent.setPackage(packageNames[0]);
739             mAppSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT);
740 
741             final boolean matchFound = pm.resolveActivity(mAppSettingsIntent, 0) != null;
742             mAppSettings.setEnabled(matchFound);
743 
744         } else {
745             mAppSettingsIntent = null;
746             mAppSettings.setEnabled(false);
747         }
748 
749         updateDetailData();
750 
751         if (NetworkPolicyManager.isUidValidForPolicy(context, primaryUid)
752                 && !getRestrictBackground() && isBandwidthControlEnabled()) {
753             setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background);
754             if (hasLimitedNetworks()) {
755                 setPreferenceSummary(mAppRestrictView,
756                         getString(R.string.data_usage_app_restrict_background_summary));
757             } else {
758                 setPreferenceSummary(mAppRestrictView,
759                         getString(R.string.data_usage_app_restrict_background_summary_disabled));
760             }
761 
762             mAppRestrictView.setVisibility(View.VISIBLE);
763             mAppRestrict.setChecked(getAppRestrictBackground());
764 
765         } else {
766             mAppRestrictView.setVisibility(View.GONE);
767         }
768     }
769 
setPolicyWarningBytes(long warningBytes)770     private void setPolicyWarningBytes(long warningBytes) {
771         if (LOGD) Log.d(TAG, "setPolicyWarningBytes()");
772         mPolicyEditor.setPolicyWarningBytes(mTemplate, warningBytes);
773         updatePolicy(false);
774     }
775 
setPolicyLimitBytes(long limitBytes)776     private void setPolicyLimitBytes(long limitBytes) {
777         if (LOGD) Log.d(TAG, "setPolicyLimitBytes()");
778         mPolicyEditor.setPolicyLimitBytes(mTemplate, limitBytes);
779         updatePolicy(false);
780     }
781 
782     /**
783      * Local cache of value, used to work around delay when
784      * {@link ConnectivityManager#setMobileDataEnabled(boolean)} is async.
785      */
786     private Boolean mMobileDataEnabled;
787 
isMobileDataEnabled()788     private boolean isMobileDataEnabled() {
789         if (mMobileDataEnabled != null) {
790             // TODO: deprecate and remove this once enabled flag is on policy
791             return mMobileDataEnabled;
792         } else {
793             return mConnService.getMobileDataEnabled();
794         }
795     }
796 
setMobileDataEnabled(boolean enabled)797     private void setMobileDataEnabled(boolean enabled) {
798         if (LOGD) Log.d(TAG, "setMobileDataEnabled()");
799         mConnService.setMobileDataEnabled(enabled);
800         mMobileDataEnabled = enabled;
801         updatePolicy(false);
802     }
803 
isNetworkPolicyModifiable(NetworkPolicy policy)804     private boolean isNetworkPolicyModifiable(NetworkPolicy policy) {
805         return policy != null && isBandwidthControlEnabled() && mDataEnabled.isChecked();
806     }
807 
isBandwidthControlEnabled()808     private boolean isBandwidthControlEnabled() {
809         try {
810             return mNetworkService.isBandwidthControlEnabled();
811         } catch (RemoteException e) {
812             Log.w(TAG, "problem talking with INetworkManagementService: " + e);
813             return false;
814         }
815     }
816 
getDataRoaming()817     private boolean getDataRoaming() {
818         final ContentResolver resolver = getActivity().getContentResolver();
819         return Settings.Secure.getInt(resolver, Settings.Secure.DATA_ROAMING, 0) != 0;
820     }
821 
setDataRoaming(boolean enabled)822     private void setDataRoaming(boolean enabled) {
823         // TODO: teach telephony DataConnectionTracker to watch and apply
824         // updates when changed.
825         final ContentResolver resolver = getActivity().getContentResolver();
826         Settings.Secure.putInt(resolver, Settings.Secure.DATA_ROAMING, enabled ? 1 : 0);
827         mMenuDataRoaming.setChecked(enabled);
828     }
829 
getRestrictBackground()830     private boolean getRestrictBackground() {
831         try {
832             return mPolicyService.getRestrictBackground();
833         } catch (RemoteException e) {
834             Log.w(TAG, "problem talking with policy service: " + e);
835             return false;
836         }
837     }
838 
setRestrictBackground(boolean restrictBackground)839     private void setRestrictBackground(boolean restrictBackground) {
840         if (LOGD) Log.d(TAG, "setRestrictBackground()");
841         try {
842             mPolicyService.setRestrictBackground(restrictBackground);
843             mMenuRestrictBackground.setChecked(restrictBackground);
844         } catch (RemoteException e) {
845             Log.w(TAG, "problem talking with policy service: " + e);
846         }
847     }
848 
getAppRestrictBackground()849     private boolean getAppRestrictBackground() {
850         final int primaryUid = getAppDetailPrimaryUid();
851         final int uidPolicy;
852         try {
853             uidPolicy = mPolicyService.getUidPolicy(primaryUid);
854         } catch (RemoteException e) {
855             // since we can't do much without policy, we bail hard.
856             throw new RuntimeException("problem reading network policy", e);
857         }
858 
859         return (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
860     }
861 
setAppRestrictBackground(boolean restrictBackground)862     private void setAppRestrictBackground(boolean restrictBackground) {
863         if (LOGD) Log.d(TAG, "setAppRestrictBackground()");
864         final int primaryUid = getAppDetailPrimaryUid();
865         try {
866             mPolicyService.setUidPolicy(primaryUid,
867                     restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE);
868         } catch (RemoteException e) {
869             throw new RuntimeException("unable to save policy", e);
870         }
871 
872         mAppRestrict.setChecked(restrictBackground);
873     }
874 
875     /**
876      * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for
877      * current {@link #mTemplate}.
878      */
updatePolicy(boolean refreshCycle)879     private void updatePolicy(boolean refreshCycle) {
880         if (isAppDetailMode()) {
881             mNetworkSwitches.setVisibility(View.GONE);
882         } else {
883             mNetworkSwitches.setVisibility(View.VISIBLE);
884         }
885 
886         // TODO: move enabled state directly into policy
887         if (TAB_MOBILE.equals(mCurrentTab)) {
888             mBinding = true;
889             mDataEnabled.setChecked(isMobileDataEnabled());
890             mBinding = false;
891         }
892 
893         final NetworkPolicy policy = mPolicyEditor.getPolicy(mTemplate);
894         if (isNetworkPolicyModifiable(policy)) {
895             mDisableAtLimitView.setVisibility(View.VISIBLE);
896             mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED);
897             if (!isAppDetailMode()) {
898                 mChart.bindNetworkPolicy(policy);
899             }
900 
901         } else {
902             // controls are disabled; don't bind warning/limit sweeps
903             mDisableAtLimitView.setVisibility(View.GONE);
904             mChart.bindNetworkPolicy(null);
905         }
906 
907         if (refreshCycle) {
908             // generate cycle list based on policy and available history
909             updateCycleList(policy);
910         }
911     }
912 
913     /**
914      * Rebuild {@link #mCycleAdapter} based on {@link NetworkPolicy#cycleDay}
915      * and available {@link NetworkStatsHistory} data. Always selects the newest
916      * item, updating the inspection range on {@link #mChart}.
917      */
updateCycleList(NetworkPolicy policy)918     private void updateCycleList(NetworkPolicy policy) {
919         // stash away currently selected cycle to try restoring below
920         final CycleItem previousItem = (CycleItem) mCycleSpinner.getSelectedItem();
921         mCycleAdapter.clear();
922 
923         final Context context = mCycleSpinner.getContext();
924 
925         long historyStart = Long.MAX_VALUE;
926         long historyEnd = Long.MIN_VALUE;
927         if (mChartData != null) {
928             historyStart = mChartData.network.getStart();
929             historyEnd = mChartData.network.getEnd();
930         }
931 
932         final long now = System.currentTimeMillis();
933         if (historyStart == Long.MAX_VALUE) historyStart = now;
934         if (historyEnd == Long.MIN_VALUE) historyEnd = now + 1;
935 
936         boolean hasCycles = false;
937         if (policy != null) {
938             // find the next cycle boundary
939             long cycleEnd = computeNextCycleBoundary(historyEnd, policy);
940 
941             // walk backwards, generating all valid cycle ranges
942             while (cycleEnd > historyStart) {
943                 final long cycleStart = computeLastCycleBoundary(cycleEnd, policy);
944                 Log.d(TAG, "generating cs=" + cycleStart + " to ce=" + cycleEnd + " waiting for hs="
945                         + historyStart);
946                 mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd));
947                 cycleEnd = cycleStart;
948                 hasCycles = true;
949             }
950 
951             // one last cycle entry to modify policy cycle day
952             mCycleAdapter.setChangePossible(isNetworkPolicyModifiable(policy));
953         }
954 
955         if (!hasCycles) {
956             // no policy defined cycles; show entry for each four-week period
957             long cycleEnd = historyEnd;
958             while (cycleEnd > historyStart) {
959                 final long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4);
960                 mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd));
961                 cycleEnd = cycleStart;
962             }
963 
964             mCycleAdapter.setChangePossible(false);
965         }
966 
967         // force pick the current cycle (first item)
968         if (mCycleAdapter.getCount() > 0) {
969             final int position = mCycleAdapter.findNearestPosition(previousItem);
970             mCycleSpinner.setSelection(position);
971 
972             // only force-update cycle when changed; skipping preserves any
973             // user-defined inspection region.
974             final CycleItem selectedItem = mCycleAdapter.getItem(position);
975             if (!Objects.equal(selectedItem, previousItem)) {
976                 mCycleListener.onItemSelected(mCycleSpinner, null, position, 0);
977             } else {
978                 // but still kick off loader for detailed list
979                 updateDetailData();
980             }
981         } else {
982             updateDetailData();
983         }
984     }
985 
986     private OnCheckedChangeListener mDataEnabledListener = new OnCheckedChangeListener() {
987         /** {@inheritDoc} */
988         public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
989             if (mBinding) return;
990 
991             final boolean dataEnabled = isChecked;
992             final String currentTab = mCurrentTab;
993             if (TAB_MOBILE.equals(currentTab)) {
994                 if (dataEnabled) {
995                     setMobileDataEnabled(true);
996                 } else {
997                     // disabling data; show confirmation dialog which eventually
998                     // calls setMobileDataEnabled() once user confirms.
999                     ConfirmDataDisableFragment.show(DataUsageSummary.this);
1000                 }
1001             }
1002 
1003             updatePolicy(false);
1004         }
1005     };
1006 
1007     private View.OnClickListener mDisableAtLimitListener = new View.OnClickListener() {
1008         /** {@inheritDoc} */
1009         public void onClick(View v) {
1010             final boolean disableAtLimit = !mDisableAtLimit.isChecked();
1011             if (disableAtLimit) {
1012                 // enabling limit; show confirmation dialog which eventually
1013                 // calls setPolicyLimitBytes() once user confirms.
1014                 ConfirmLimitFragment.show(DataUsageSummary.this);
1015             } else {
1016                 setPolicyLimitBytes(LIMIT_DISABLED);
1017             }
1018         }
1019     };
1020 
1021     private View.OnClickListener mAppRestrictListener = new View.OnClickListener() {
1022         /** {@inheritDoc} */
1023         public void onClick(View v) {
1024             final boolean restrictBackground = !mAppRestrict.isChecked();
1025 
1026             if (restrictBackground) {
1027                 if (hasLimitedNetworks()) {
1028                     // enabling restriction; show confirmation dialog which
1029                     // eventually calls setRestrictBackground() once user
1030                     // confirms.
1031                     ConfirmAppRestrictFragment.show(DataUsageSummary.this);
1032                 } else {
1033                     // no limited networks; show dialog to guide user towards
1034                     // setting a network limit. doesn't mutate restrict state.
1035                     DeniedRestrictFragment.show(DataUsageSummary.this);
1036                 }
1037             } else {
1038                 setAppRestrictBackground(false);
1039             }
1040         }
1041     };
1042 
1043     private OnClickListener mAppSettingsListener = new OnClickListener() {
1044         /** {@inheritDoc} */
1045         public void onClick(View v) {
1046             // TODO: target torwards entire UID instead of just first package
1047             startActivity(mAppSettingsIntent);
1048         }
1049     };
1050 
1051     private OnItemClickListener mListListener = new OnItemClickListener() {
1052         /** {@inheritDoc} */
1053         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
1054             final Context context = view.getContext();
1055             final AppUsageItem app = (AppUsageItem) parent.getItemAtPosition(position);
1056             final UidDetail detail = mUidDetailProvider.getUidDetail(app.uids[0], true);
1057             AppDetailsFragment.show(DataUsageSummary.this, app.uids, detail.label);
1058         }
1059     };
1060 
1061     private OnItemSelectedListener mCycleListener = new OnItemSelectedListener() {
1062         /** {@inheritDoc} */
1063         public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
1064             final CycleItem cycle = (CycleItem) parent.getItemAtPosition(position);
1065             if (cycle instanceof CycleChangeItem) {
1066                 // show cycle editor; will eventually call setPolicyCycleDay()
1067                 // when user finishes editing.
1068                 CycleEditorFragment.show(DataUsageSummary.this);
1069 
1070                 // reset spinner to something other than "change cycle..."
1071                 mCycleSpinner.setSelection(0);
1072 
1073             } else {
1074                 if (LOGD) {
1075                     Log.d(TAG, "showing cycle " + cycle + ", start=" + cycle.start + ", end="
1076                             + cycle.end + "]");
1077                 }
1078 
1079                 // update chart to show selected cycle, and update detail data
1080                 // to match updated sweep bounds.
1081                 mChart.setVisibleRange(cycle.start, cycle.end);
1082 
1083                 updateDetailData();
1084             }
1085         }
1086 
1087         /** {@inheritDoc} */
1088         public void onNothingSelected(AdapterView<?> parent) {
1089             // ignored
1090         }
1091     };
1092 
1093     /**
1094      * Update details based on {@link #mChart} inspection range depending on
1095      * current mode. In network mode, updates {@link #mAdapter} with sorted list
1096      * of applications data usage, and when {@link #isAppDetailMode()} update
1097      * app details.
1098      */
updateDetailData()1099     private void updateDetailData() {
1100         if (LOGD) Log.d(TAG, "updateDetailData()");
1101 
1102         final long start = mChart.getInspectStart();
1103         final long end = mChart.getInspectEnd();
1104         final long now = System.currentTimeMillis();
1105 
1106         final Context context = getActivity();
1107 
1108         NetworkStatsHistory.Entry entry = null;
1109         if (isAppDetailMode() && mChartData != null && mChartData.detail != null) {
1110             // bind foreground/background to piechart and labels
1111             entry = mChartData.detailDefault.getValues(start, end, now, entry);
1112             final long defaultBytes = entry.rxBytes + entry.txBytes;
1113             entry = mChartData.detailForeground.getValues(start, end, now, entry);
1114             final long foregroundBytes = entry.rxBytes + entry.txBytes;
1115 
1116             mAppPieChart.setOriginAngle(175);
1117 
1118             mAppPieChart.removeAllSlices();
1119             mAppPieChart.addSlice(foregroundBytes, Color.parseColor("#d88d3a"));
1120             mAppPieChart.addSlice(defaultBytes, Color.parseColor("#666666"));
1121 
1122             mAppPieChart.generatePath();
1123 
1124             mAppBackground.setText(Formatter.formatFileSize(context, defaultBytes));
1125             mAppForeground.setText(Formatter.formatFileSize(context, foregroundBytes));
1126 
1127             // and finally leave with summary data for label below
1128             entry = mChartData.detail.getValues(start, end, now, null);
1129 
1130             getLoaderManager().destroyLoader(LOADER_SUMMARY);
1131 
1132         } else {
1133             if (mChartData != null) {
1134                 entry = mChartData.network.getValues(start, end, now, null);
1135             }
1136 
1137             // kick off loader for detailed stats
1138             getLoaderManager().restartLoader(LOADER_SUMMARY,
1139                     SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryCallbacks);
1140         }
1141 
1142         final long totalBytes = entry != null ? entry.rxBytes + entry.txBytes : 0;
1143         final String totalPhrase = Formatter.formatFileSize(context, totalBytes);
1144         final String rangePhrase = formatDateRange(context, start, end, false);
1145 
1146         mUsageSummary.setText(
1147                 getString(R.string.data_usage_total_during_range, totalPhrase, rangePhrase));
1148     }
1149 
1150     private final LoaderCallbacks<ChartData> mChartDataCallbacks = new LoaderCallbacks<
1151             ChartData>() {
1152         /** {@inheritDoc} */
1153         public Loader<ChartData> onCreateLoader(int id, Bundle args) {
1154             return new ChartDataLoader(getActivity(), mStatsService, args);
1155         }
1156 
1157         /** {@inheritDoc} */
1158         public void onLoadFinished(Loader<ChartData> loader, ChartData data) {
1159             mChartData = data;
1160             mChart.bindNetworkStats(mChartData.network);
1161             mChart.bindDetailNetworkStats(mChartData.detail);
1162 
1163             // calcuate policy cycles based on available data
1164             updatePolicy(true);
1165             updateAppDetail();
1166 
1167             // force scroll to top of body when showing detail
1168             if (mChartData.detail != null) {
1169                 mListView.smoothScrollToPosition(0);
1170             }
1171         }
1172 
1173         /** {@inheritDoc} */
1174         public void onLoaderReset(Loader<ChartData> loader) {
1175             mChartData = null;
1176             mChart.bindNetworkStats(null);
1177             mChart.bindDetailNetworkStats(null);
1178         }
1179     };
1180 
1181     private final LoaderCallbacks<NetworkStats> mSummaryCallbacks = new LoaderCallbacks<
1182             NetworkStats>() {
1183         /** {@inheritDoc} */
1184         public Loader<NetworkStats> onCreateLoader(int id, Bundle args) {
1185             return new SummaryForAllUidLoader(getActivity(), mStatsService, args);
1186         }
1187 
1188         /** {@inheritDoc} */
1189         public void onLoadFinished(Loader<NetworkStats> loader, NetworkStats data) {
1190             mAdapter.bindStats(data);
1191             updateEmptyVisible();
1192         }
1193 
1194         /** {@inheritDoc} */
1195         public void onLoaderReset(Loader<NetworkStats> loader) {
1196             mAdapter.bindStats(null);
1197             updateEmptyVisible();
1198         }
1199 
1200         private void updateEmptyVisible() {
1201             final boolean isEmpty = mAdapter.isEmpty() && !isAppDetailMode();
1202             mEmpty.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
1203         }
1204     };
1205 
isMobilePolicySplit()1206     private boolean isMobilePolicySplit() {
1207         final Context context = getActivity();
1208         if (hasMobileRadio(context)) {
1209             final String subscriberId = getActiveSubscriberId(context);
1210             return mPolicyEditor.isMobilePolicySplit(subscriberId);
1211         } else {
1212             return false;
1213         }
1214     }
1215 
setMobilePolicySplit(boolean split)1216     private void setMobilePolicySplit(boolean split) {
1217         final String subscriberId = getActiveSubscriberId(getActivity());
1218         mPolicyEditor.setMobilePolicySplit(subscriberId, split);
1219     }
1220 
getActiveSubscriberId(Context context)1221     private static String getActiveSubscriberId(Context context) {
1222         final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
1223                 Context.TELEPHONY_SERVICE);
1224         return telephony.getSubscriberId();
1225     }
1226 
1227     private DataUsageChartListener mChartListener = new DataUsageChartListener() {
1228         /** {@inheritDoc} */
1229         public void onInspectRangeChanged() {
1230             if (LOGD) Log.d(TAG, "onInspectRangeChanged()");
1231             updateDetailData();
1232         }
1233 
1234         /** {@inheritDoc} */
1235         public void onWarningChanged() {
1236             setPolicyWarningBytes(mChart.getWarningBytes());
1237         }
1238 
1239         /** {@inheritDoc} */
1240         public void onLimitChanged() {
1241             setPolicyLimitBytes(mChart.getLimitBytes());
1242         }
1243 
1244         /** {@inheritDoc} */
1245         public void requestWarningEdit() {
1246             WarningEditorFragment.show(DataUsageSummary.this);
1247         }
1248 
1249         /** {@inheritDoc} */
1250         public void requestLimitEdit() {
1251             LimitEditorFragment.show(DataUsageSummary.this);
1252         }
1253     };
1254 
1255     /**
1256      * List item that reflects a specific data usage cycle.
1257      */
1258     public static class CycleItem implements Comparable<CycleItem> {
1259         public CharSequence label;
1260         public long start;
1261         public long end;
1262 
CycleItem(CharSequence label)1263         CycleItem(CharSequence label) {
1264             this.label = label;
1265         }
1266 
CycleItem(Context context, long start, long end)1267         public CycleItem(Context context, long start, long end) {
1268             this.label = formatDateRange(context, start, end, true);
1269             this.start = start;
1270             this.end = end;
1271         }
1272 
1273         @Override
toString()1274         public String toString() {
1275             return label.toString();
1276         }
1277 
1278         @Override
equals(Object o)1279         public boolean equals(Object o) {
1280             if (o instanceof CycleItem) {
1281                 final CycleItem another = (CycleItem) o;
1282                 return start == another.start && end == another.end;
1283             }
1284             return false;
1285         }
1286 
1287         /** {@inheritDoc} */
compareTo(CycleItem another)1288         public int compareTo(CycleItem another) {
1289             return Long.compare(start, another.start);
1290         }
1291     }
1292 
1293     private static final StringBuilder sBuilder = new StringBuilder(50);
1294     private static final java.util.Formatter sFormatter = new java.util.Formatter(
1295             sBuilder, Locale.getDefault());
1296 
formatDateRange(Context context, long start, long end, boolean utcTime)1297     public static String formatDateRange(Context context, long start, long end, boolean utcTime) {
1298         final int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH;
1299         final String timezone = utcTime ? TIMEZONE_UTC : null;
1300 
1301         synchronized (sBuilder) {
1302             sBuilder.setLength(0);
1303             return DateUtils
1304                     .formatDateRange(context, sFormatter, start, end, flags, timezone).toString();
1305         }
1306     }
1307 
1308     /**
1309      * Special-case data usage cycle that triggers dialog to change
1310      * {@link NetworkPolicy#cycleDay}.
1311      */
1312     public static class CycleChangeItem extends CycleItem {
CycleChangeItem(Context context)1313         public CycleChangeItem(Context context) {
1314             super(context.getString(R.string.data_usage_change_cycle));
1315         }
1316     }
1317 
1318     public static class CycleAdapter extends ArrayAdapter<CycleItem> {
1319         private boolean mChangePossible = false;
1320         private boolean mChangeVisible = false;
1321 
1322         private final CycleChangeItem mChangeItem;
1323 
CycleAdapter(Context context)1324         public CycleAdapter(Context context) {
1325             super(context, android.R.layout.simple_spinner_item);
1326             setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
1327             mChangeItem = new CycleChangeItem(context);
1328         }
1329 
setChangePossible(boolean possible)1330         public void setChangePossible(boolean possible) {
1331             mChangePossible = possible;
1332             updateChange();
1333         }
1334 
setChangeVisible(boolean visible)1335         public void setChangeVisible(boolean visible) {
1336             mChangeVisible = visible;
1337             updateChange();
1338         }
1339 
updateChange()1340         private void updateChange() {
1341             remove(mChangeItem);
1342             if (mChangePossible && mChangeVisible) {
1343                 add(mChangeItem);
1344             }
1345         }
1346 
1347         /**
1348          * Find position of {@link CycleItem} in this adapter which is nearest
1349          * the given {@link CycleItem}.
1350          */
findNearestPosition(CycleItem target)1351         public int findNearestPosition(CycleItem target) {
1352             if (target != null) {
1353                 final int count = getCount();
1354                 for (int i = count - 1; i >= 0; i--) {
1355                     final CycleItem item = getItem(i);
1356                     if (item instanceof CycleChangeItem) {
1357                         continue;
1358                     } else if (item.compareTo(target) >= 0) {
1359                         return i;
1360                     }
1361                 }
1362             }
1363             return 0;
1364         }
1365     }
1366 
1367     private static class AppUsageItem implements Comparable<AppUsageItem> {
1368         public int[] uids;
1369         public long total;
1370 
AppUsageItem(int uid)1371         public AppUsageItem(int uid) {
1372             uids = new int[] { uid };
1373         }
1374 
addUid(int uid)1375         public void addUid(int uid) {
1376             if (contains(uids, uid)) return;
1377             final int length = uids.length;
1378             uids = Arrays.copyOf(uids, length + 1);
1379             uids[length] = uid;
1380         }
1381 
1382         /** {@inheritDoc} */
compareTo(AppUsageItem another)1383         public int compareTo(AppUsageItem another) {
1384             return Long.compare(another.total, total);
1385         }
1386     }
1387 
1388     /**
1389      * Adapter of applications, sorted by total usage descending.
1390      */
1391     public static class DataUsageAdapter extends BaseAdapter {
1392         private final UidDetailProvider mProvider;
1393         private final int mInsetSide;
1394 
1395         private ArrayList<AppUsageItem> mItems = Lists.newArrayList();
1396         private long mLargest;
1397 
DataUsageAdapter(UidDetailProvider provider, int insetSide)1398         public DataUsageAdapter(UidDetailProvider provider, int insetSide) {
1399             mProvider = checkNotNull(provider);
1400             mInsetSide = insetSide;
1401         }
1402 
1403         /**
1404          * Bind the given {@link NetworkStats}, or {@code null} to clear list.
1405          */
bindStats(NetworkStats stats)1406         public void bindStats(NetworkStats stats) {
1407             mItems.clear();
1408 
1409             final AppUsageItem systemItem = new AppUsageItem(android.os.Process.SYSTEM_UID);
1410             final SparseArray<AppUsageItem> knownUids = new SparseArray<AppUsageItem>();
1411 
1412             NetworkStats.Entry entry = null;
1413             final int size = stats != null ? stats.size() : 0;
1414             for (int i = 0; i < size; i++) {
1415                 entry = stats.getValues(i, entry);
1416 
1417                 final int uid = entry.uid;
1418                 final boolean isApp = uid >= android.os.Process.FIRST_APPLICATION_UID
1419                         && uid <= android.os.Process.LAST_APPLICATION_UID;
1420                 if (isApp || uid == UID_REMOVED || uid == UID_TETHERING) {
1421                     AppUsageItem item = knownUids.get(uid);
1422                     if (item == null) {
1423                         item = new AppUsageItem(uid);
1424                         knownUids.put(uid, item);
1425                         mItems.add(item);
1426                     }
1427 
1428                     item.total += entry.rxBytes + entry.txBytes;
1429                 } else {
1430                     systemItem.total += entry.rxBytes + entry.txBytes;
1431                     systemItem.addUid(uid);
1432                 }
1433             }
1434 
1435             if (systemItem.total > 0) {
1436                 mItems.add(systemItem);
1437             }
1438 
1439             Collections.sort(mItems);
1440             mLargest = (mItems.size() > 0) ? mItems.get(0).total : 0;
1441             notifyDataSetChanged();
1442         }
1443 
1444         @Override
getCount()1445         public int getCount() {
1446             return mItems.size();
1447         }
1448 
1449         @Override
getItem(int position)1450         public Object getItem(int position) {
1451             return mItems.get(position);
1452         }
1453 
1454         @Override
getItemId(int position)1455         public long getItemId(int position) {
1456             return mItems.get(position).uids[0];
1457         }
1458 
1459         @Override
getView(int position, View convertView, ViewGroup parent)1460         public View getView(int position, View convertView, ViewGroup parent) {
1461             if (convertView == null) {
1462                 convertView = LayoutInflater.from(parent.getContext()).inflate(
1463                         R.layout.data_usage_item, parent, false);
1464 
1465                 if (mInsetSide > 0) {
1466                     convertView.setPadding(mInsetSide, 0, mInsetSide, 0);
1467                 }
1468             }
1469 
1470             final Context context = parent.getContext();
1471 
1472             final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
1473             final ProgressBar progress = (ProgressBar) convertView.findViewById(
1474                     android.R.id.progress);
1475 
1476             // kick off async load of app details
1477             final AppUsageItem item = mItems.get(position);
1478             UidDetailTask.bindView(mProvider, item, convertView);
1479 
1480             text1.setText(Formatter.formatFileSize(context, item.total));
1481 
1482             final int percentTotal = mLargest != 0 ? (int) (item.total * 100 / mLargest) : 0;
1483             progress.setProgress(percentTotal);
1484 
1485             return convertView;
1486         }
1487     }
1488 
1489     /**
1490      * Empty {@link Fragment} that controls display of UID details in
1491      * {@link DataUsageSummary}.
1492      */
1493     public static class AppDetailsFragment extends Fragment {
1494         private static final String EXTRA_UIDS = "uids";
1495 
show(DataUsageSummary parent, int[] uids, CharSequence label)1496         public static void show(DataUsageSummary parent, int[] uids, CharSequence label) {
1497             if (!parent.isAdded()) return;
1498 
1499             final Bundle args = new Bundle();
1500             args.putIntArray(EXTRA_UIDS, uids);
1501 
1502             final AppDetailsFragment fragment = new AppDetailsFragment();
1503             fragment.setArguments(args);
1504             fragment.setTargetFragment(parent, 0);
1505 
1506             final FragmentTransaction ft = parent.getFragmentManager().beginTransaction();
1507             ft.add(fragment, TAG_APP_DETAILS);
1508             ft.addToBackStack(TAG_APP_DETAILS);
1509             ft.setBreadCrumbTitle(label);
1510             ft.commit();
1511         }
1512 
1513         @Override
onStart()1514         public void onStart() {
1515             super.onStart();
1516             final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
1517             target.mAppDetailUids = getArguments().getIntArray(EXTRA_UIDS);
1518             target.updateBody();
1519         }
1520 
1521         @Override
onStop()1522         public void onStop() {
1523             super.onStop();
1524             final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
1525             target.mAppDetailUids = null;
1526             target.updateBody();
1527         }
1528     }
1529 
1530     /**
1531      * Dialog to request user confirmation before setting
1532      * {@link NetworkPolicy#limitBytes}.
1533      */
1534     public static class ConfirmLimitFragment extends DialogFragment {
1535         private static final String EXTRA_MESSAGE = "message";
1536         private static final String EXTRA_LIMIT_BYTES = "limitBytes";
1537 
show(DataUsageSummary parent)1538         public static void show(DataUsageSummary parent) {
1539             if (!parent.isAdded()) return;
1540 
1541             final Resources res = parent.getResources();
1542             final CharSequence message;
1543             final long limitBytes;
1544 
1545             // TODO: customize default limits based on network template
1546             final String currentTab = parent.mCurrentTab;
1547             if (TAB_3G.equals(currentTab)) {
1548                 message = buildDialogMessage(res, R.string.data_usage_tab_3g);
1549                 limitBytes = 5 * GB_IN_BYTES;
1550             } else if (TAB_4G.equals(currentTab)) {
1551                 message = buildDialogMessage(res, R.string.data_usage_tab_4g);
1552                 limitBytes = 5 * GB_IN_BYTES;
1553             } else if (TAB_MOBILE.equals(currentTab)) {
1554                 message = buildDialogMessage(res, R.string.data_usage_list_mobile);
1555                 limitBytes = 5 * GB_IN_BYTES;
1556             } else if (TAB_WIFI.equals(currentTab)) {
1557                 message = buildDialogMessage(res, R.string.data_usage_tab_wifi);
1558                 limitBytes = 5 * GB_IN_BYTES;
1559             } else {
1560                 throw new IllegalArgumentException("unknown current tab: " + currentTab);
1561             }
1562 
1563             final Bundle args = new Bundle();
1564             args.putCharSequence(EXTRA_MESSAGE, message);
1565             args.putLong(EXTRA_LIMIT_BYTES, limitBytes);
1566 
1567             final ConfirmLimitFragment dialog = new ConfirmLimitFragment();
1568             dialog.setArguments(args);
1569             dialog.setTargetFragment(parent, 0);
1570             dialog.show(parent.getFragmentManager(), TAG_CONFIRM_LIMIT);
1571         }
1572 
buildDialogMessage(Resources res, int networkResId)1573         private static CharSequence buildDialogMessage(Resources res, int networkResId) {
1574             return res.getString(R.string.data_usage_limit_dialog, res.getString(networkResId));
1575         }
1576 
1577         @Override
onCreateDialog(Bundle savedInstanceState)1578         public Dialog onCreateDialog(Bundle savedInstanceState) {
1579             final Context context = getActivity();
1580 
1581             final CharSequence message = getArguments().getCharSequence(EXTRA_MESSAGE);
1582             final long limitBytes = getArguments().getLong(EXTRA_LIMIT_BYTES);
1583 
1584             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
1585             builder.setTitle(R.string.data_usage_limit_dialog_title);
1586             builder.setMessage(message);
1587 
1588             builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
1589                 public void onClick(DialogInterface dialog, int which) {
1590                     final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
1591                     if (target != null) {
1592                         target.setPolicyLimitBytes(limitBytes);
1593                     }
1594                 }
1595             });
1596 
1597             return builder.create();
1598         }
1599     }
1600 
1601     /**
1602      * Dialog to edit {@link NetworkPolicy#cycleDay}.
1603      */
1604     public static class CycleEditorFragment extends DialogFragment {
1605         private static final String EXTRA_TEMPLATE = "template";
1606 
show(DataUsageSummary parent)1607         public static void show(DataUsageSummary parent) {
1608             if (!parent.isAdded()) return;
1609 
1610             final Bundle args = new Bundle();
1611             args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate);
1612 
1613             final CycleEditorFragment dialog = new CycleEditorFragment();
1614             dialog.setArguments(args);
1615             dialog.setTargetFragment(parent, 0);
1616             dialog.show(parent.getFragmentManager(), TAG_CYCLE_EDITOR);
1617         }
1618 
1619         @Override
onCreateDialog(Bundle savedInstanceState)1620         public Dialog onCreateDialog(Bundle savedInstanceState) {
1621             final Context context = getActivity();
1622             final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
1623             final NetworkPolicyEditor editor = target.mPolicyEditor;
1624 
1625             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
1626             final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
1627 
1628             final View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false);
1629             final NumberPicker cycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day);
1630 
1631             final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
1632             final int cycleDay = editor.getPolicyCycleDay(template);
1633 
1634             cycleDayPicker.setMinValue(1);
1635             cycleDayPicker.setMaxValue(31);
1636             cycleDayPicker.setValue(cycleDay);
1637             cycleDayPicker.setWrapSelectorWheel(true);
1638 
1639             builder.setTitle(R.string.data_usage_cycle_editor_title);
1640             builder.setView(view);
1641 
1642             builder.setPositiveButton(R.string.data_usage_cycle_editor_positive,
1643                     new DialogInterface.OnClickListener() {
1644                         public void onClick(DialogInterface dialog, int which) {
1645                             final int cycleDay = cycleDayPicker.getValue();
1646                             editor.setPolicyCycleDay(template, cycleDay);
1647                             target.updatePolicy(true);
1648                         }
1649                     });
1650 
1651             return builder.create();
1652         }
1653     }
1654 
1655     /**
1656      * Dialog to edit {@link NetworkPolicy#warningBytes}.
1657      */
1658     public static class WarningEditorFragment extends DialogFragment {
1659         private static final String EXTRA_TEMPLATE = "template";
1660 
show(DataUsageSummary parent)1661         public static void show(DataUsageSummary parent) {
1662             if (!parent.isAdded()) return;
1663 
1664             final Bundle args = new Bundle();
1665             args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate);
1666 
1667             final WarningEditorFragment dialog = new WarningEditorFragment();
1668             dialog.setArguments(args);
1669             dialog.setTargetFragment(parent, 0);
1670             dialog.show(parent.getFragmentManager(), TAG_WARNING_EDITOR);
1671         }
1672 
1673         @Override
onCreateDialog(Bundle savedInstanceState)1674         public Dialog onCreateDialog(Bundle savedInstanceState) {
1675             final Context context = getActivity();
1676             final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
1677             final NetworkPolicyEditor editor = target.mPolicyEditor;
1678 
1679             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
1680             final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
1681 
1682             final View view = dialogInflater.inflate(R.layout.data_usage_bytes_editor, null, false);
1683             final NumberPicker bytesPicker = (NumberPicker) view.findViewById(R.id.bytes);
1684 
1685             final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
1686             final long warningBytes = editor.getPolicyWarningBytes(template);
1687             final long limitBytes = editor.getPolicyLimitBytes(template);
1688 
1689             bytesPicker.setMinValue(0);
1690             if (limitBytes != LIMIT_DISABLED) {
1691                 bytesPicker.setMaxValue((int) (limitBytes / MB_IN_BYTES) - 1);
1692             } else {
1693                 bytesPicker.setMaxValue(Integer.MAX_VALUE);
1694             }
1695             bytesPicker.setValue((int) (warningBytes / MB_IN_BYTES));
1696             bytesPicker.setWrapSelectorWheel(false);
1697 
1698             builder.setTitle(R.string.data_usage_warning_editor_title);
1699             builder.setView(view);
1700 
1701             builder.setPositiveButton(R.string.data_usage_cycle_editor_positive,
1702                     new DialogInterface.OnClickListener() {
1703                         public void onClick(DialogInterface dialog, int which) {
1704                             // clear focus to finish pending text edits
1705                             bytesPicker.clearFocus();
1706 
1707                             final long bytes = bytesPicker.getValue() * MB_IN_BYTES;
1708                             editor.setPolicyWarningBytes(template, bytes);
1709                             target.updatePolicy(false);
1710                         }
1711                     });
1712 
1713             return builder.create();
1714         }
1715     }
1716 
1717     /**
1718      * Dialog to edit {@link NetworkPolicy#limitBytes}.
1719      */
1720     public static class LimitEditorFragment extends DialogFragment {
1721         private static final String EXTRA_TEMPLATE = "template";
1722 
show(DataUsageSummary parent)1723         public static void show(DataUsageSummary parent) {
1724             if (!parent.isAdded()) return;
1725 
1726             final Bundle args = new Bundle();
1727             args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate);
1728 
1729             final LimitEditorFragment dialog = new LimitEditorFragment();
1730             dialog.setArguments(args);
1731             dialog.setTargetFragment(parent, 0);
1732             dialog.show(parent.getFragmentManager(), TAG_LIMIT_EDITOR);
1733         }
1734 
1735         @Override
onCreateDialog(Bundle savedInstanceState)1736         public Dialog onCreateDialog(Bundle savedInstanceState) {
1737             final Context context = getActivity();
1738             final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
1739             final NetworkPolicyEditor editor = target.mPolicyEditor;
1740 
1741             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
1742             final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
1743 
1744             final View view = dialogInflater.inflate(R.layout.data_usage_bytes_editor, null, false);
1745             final NumberPicker bytesPicker = (NumberPicker) view.findViewById(R.id.bytes);
1746 
1747             final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
1748             final long warningBytes = editor.getPolicyWarningBytes(template);
1749             final long limitBytes = editor.getPolicyLimitBytes(template);
1750 
1751             bytesPicker.setMaxValue(Integer.MAX_VALUE);
1752             if (warningBytes != WARNING_DISABLED) {
1753                 bytesPicker.setMinValue((int) (warningBytes / MB_IN_BYTES) + 1);
1754             } else {
1755                 bytesPicker.setMinValue(0);
1756             }
1757             bytesPicker.setValue((int) (limitBytes / MB_IN_BYTES));
1758             bytesPicker.setWrapSelectorWheel(false);
1759 
1760             builder.setTitle(R.string.data_usage_limit_editor_title);
1761             builder.setView(view);
1762 
1763             builder.setPositiveButton(R.string.data_usage_cycle_editor_positive,
1764                     new DialogInterface.OnClickListener() {
1765                         public void onClick(DialogInterface dialog, int which) {
1766                             // clear focus to finish pending text edits
1767                             bytesPicker.clearFocus();
1768 
1769                             final long bytes = bytesPicker.getValue() * MB_IN_BYTES;
1770                             editor.setPolicyLimitBytes(template, bytes);
1771                             target.updatePolicy(false);
1772                         }
1773                     });
1774 
1775             return builder.create();
1776         }
1777     }
1778     /**
1779      * Dialog to request user confirmation before disabling data.
1780      */
1781     public static class ConfirmDataDisableFragment extends DialogFragment {
show(DataUsageSummary parent)1782         public static void show(DataUsageSummary parent) {
1783             if (!parent.isAdded()) return;
1784 
1785             final ConfirmDataDisableFragment dialog = new ConfirmDataDisableFragment();
1786             dialog.setTargetFragment(parent, 0);
1787             dialog.show(parent.getFragmentManager(), TAG_CONFIRM_DATA_DISABLE);
1788         }
1789 
1790         @Override
onCreateDialog(Bundle savedInstanceState)1791         public Dialog onCreateDialog(Bundle savedInstanceState) {
1792             final Context context = getActivity();
1793 
1794             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
1795             builder.setMessage(R.string.data_usage_disable_mobile);
1796 
1797             builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
1798                 public void onClick(DialogInterface dialog, int which) {
1799                     final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
1800                     if (target != null) {
1801                         // TODO: extend to modify policy enabled flag.
1802                         target.setMobileDataEnabled(false);
1803                     }
1804                 }
1805             });
1806             builder.setNegativeButton(android.R.string.cancel, null);
1807 
1808             return builder.create();
1809         }
1810     }
1811 
1812     /**
1813      * Dialog to request user confirmation before setting
1814      * {@link Settings.Secure#DATA_ROAMING}.
1815      */
1816     public static class ConfirmDataRoamingFragment extends DialogFragment {
show(DataUsageSummary parent)1817         public static void show(DataUsageSummary parent) {
1818             if (!parent.isAdded()) return;
1819 
1820             final ConfirmDataRoamingFragment dialog = new ConfirmDataRoamingFragment();
1821             dialog.setTargetFragment(parent, 0);
1822             dialog.show(parent.getFragmentManager(), TAG_CONFIRM_DATA_ROAMING);
1823         }
1824 
1825         @Override
onCreateDialog(Bundle savedInstanceState)1826         public Dialog onCreateDialog(Bundle savedInstanceState) {
1827             final Context context = getActivity();
1828 
1829             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
1830             builder.setTitle(R.string.roaming_reenable_title);
1831             builder.setMessage(R.string.roaming_warning);
1832 
1833             builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
1834                 public void onClick(DialogInterface dialog, int which) {
1835                     final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
1836                     if (target != null) {
1837                         target.setDataRoaming(true);
1838                     }
1839                 }
1840             });
1841             builder.setNegativeButton(android.R.string.cancel, null);
1842 
1843             return builder.create();
1844         }
1845     }
1846 
1847     /**
1848      * Dialog to request user confirmation before setting
1849      * {@link INetworkPolicyManager#setRestrictBackground(boolean)}.
1850      */
1851     public static class ConfirmRestrictFragment extends DialogFragment {
show(DataUsageSummary parent)1852         public static void show(DataUsageSummary parent) {
1853             if (!parent.isAdded()) return;
1854 
1855             final ConfirmRestrictFragment dialog = new ConfirmRestrictFragment();
1856             dialog.setTargetFragment(parent, 0);
1857             dialog.show(parent.getFragmentManager(), TAG_CONFIRM_RESTRICT);
1858         }
1859 
1860         @Override
onCreateDialog(Bundle savedInstanceState)1861         public Dialog onCreateDialog(Bundle savedInstanceState) {
1862             final Context context = getActivity();
1863 
1864             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
1865             builder.setTitle(R.string.data_usage_restrict_background_title);
1866             builder.setMessage(getString(R.string.data_usage_restrict_background));
1867 
1868             builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
1869                 public void onClick(DialogInterface dialog, int which) {
1870                     final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
1871                     if (target != null) {
1872                         target.setRestrictBackground(true);
1873                     }
1874                 }
1875             });
1876             builder.setNegativeButton(android.R.string.cancel, null);
1877 
1878             return builder.create();
1879         }
1880     }
1881 
1882     /**
1883      * Dialog to inform user that {@link #POLICY_REJECT_METERED_BACKGROUND}
1884      * change has been denied, usually based on
1885      * {@link DataUsageSummary#hasLimitedNetworks()}.
1886      */
1887     public static class DeniedRestrictFragment extends DialogFragment {
show(DataUsageSummary parent)1888         public static void show(DataUsageSummary parent) {
1889             if (!parent.isAdded()) return;
1890 
1891             final DeniedRestrictFragment dialog = new DeniedRestrictFragment();
1892             dialog.setTargetFragment(parent, 0);
1893             dialog.show(parent.getFragmentManager(), TAG_DENIED_RESTRICT);
1894         }
1895 
1896         @Override
onCreateDialog(Bundle savedInstanceState)1897         public Dialog onCreateDialog(Bundle savedInstanceState) {
1898             final Context context = getActivity();
1899 
1900             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
1901             builder.setTitle(R.string.data_usage_app_restrict_background);
1902             builder.setMessage(R.string.data_usage_restrict_denied_dialog);
1903             builder.setPositiveButton(android.R.string.ok, null);
1904 
1905             return builder.create();
1906         }
1907     }
1908 
1909     /**
1910      * Dialog to request user confirmation before setting
1911      * {@link #POLICY_REJECT_METERED_BACKGROUND}.
1912      */
1913     public static class ConfirmAppRestrictFragment extends DialogFragment {
show(DataUsageSummary parent)1914         public static void show(DataUsageSummary parent) {
1915             if (!parent.isAdded()) return;
1916 
1917             final ConfirmAppRestrictFragment dialog = new ConfirmAppRestrictFragment();
1918             dialog.setTargetFragment(parent, 0);
1919             dialog.show(parent.getFragmentManager(), TAG_CONFIRM_APP_RESTRICT);
1920         }
1921 
1922         @Override
onCreateDialog(Bundle savedInstanceState)1923         public Dialog onCreateDialog(Bundle savedInstanceState) {
1924             final Context context = getActivity();
1925 
1926             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
1927             builder.setTitle(R.string.data_usage_app_restrict_dialog_title);
1928             builder.setMessage(R.string.data_usage_app_restrict_dialog);
1929 
1930             builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
1931                 public void onClick(DialogInterface dialog, int which) {
1932                     final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
1933                     if (target != null) {
1934                         target.setAppRestrictBackground(true);
1935                     }
1936                 }
1937             });
1938             builder.setNegativeButton(android.R.string.cancel, null);
1939 
1940             return builder.create();
1941         }
1942     }
1943 
1944     /**
1945      * Compute default tab that should be selected, based on
1946      * {@link NetworkPolicyManager#EXTRA_NETWORK_TEMPLATE} extra.
1947      */
computeTabFromIntent(Intent intent)1948     private static String computeTabFromIntent(Intent intent) {
1949         final NetworkTemplate template = intent.getParcelableExtra(EXTRA_NETWORK_TEMPLATE);
1950         if (template == null) return null;
1951 
1952         switch (template.getMatchRule()) {
1953             case MATCH_MOBILE_3G_LOWER:
1954                 return TAB_3G;
1955             case MATCH_MOBILE_4G:
1956                 return TAB_4G;
1957             case MATCH_MOBILE_ALL:
1958                 return TAB_MOBILE;
1959             case MATCH_WIFI:
1960                 return TAB_WIFI;
1961             default:
1962                 return null;
1963         }
1964     }
1965 
1966     /**
1967      * Background task that loads {@link UidDetail}, binding to
1968      * {@link DataUsageAdapter} row item when finished.
1969      */
1970     private static class UidDetailTask extends AsyncTask<Void, Void, UidDetail> {
1971         private final UidDetailProvider mProvider;
1972         private final AppUsageItem mItem;
1973         private final View mTarget;
1974 
UidDetailTask(UidDetailProvider provider, AppUsageItem item, View target)1975         private UidDetailTask(UidDetailProvider provider, AppUsageItem item, View target) {
1976             mProvider = checkNotNull(provider);
1977             mItem = checkNotNull(item);
1978             mTarget = checkNotNull(target);
1979         }
1980 
bindView( UidDetailProvider provider, AppUsageItem item, View target)1981         public static void bindView(
1982                 UidDetailProvider provider, AppUsageItem item, View target) {
1983             final UidDetailTask existing = (UidDetailTask) target.getTag();
1984             if (existing != null) {
1985                 existing.cancel(false);
1986             }
1987 
1988             final UidDetail cachedDetail = provider.getUidDetail(item.uids[0], false);
1989             if (cachedDetail != null) {
1990                 bindView(cachedDetail, target);
1991             } else {
1992                 target.setTag(new UidDetailTask(provider, item, target).executeOnExecutor(
1993                         AsyncTask.THREAD_POOL_EXECUTOR));
1994             }
1995         }
1996 
bindView(UidDetail detail, View target)1997         private static void bindView(UidDetail detail, View target) {
1998             final ImageView icon = (ImageView) target.findViewById(android.R.id.icon);
1999             final TextView title = (TextView) target.findViewById(android.R.id.title);
2000 
2001             if (detail != null) {
2002                 icon.setImageDrawable(detail.icon);
2003                 title.setText(detail.label);
2004             } else {
2005                 icon.setImageDrawable(null);
2006                 title.setText(null);
2007             }
2008         }
2009 
2010         @Override
onPreExecute()2011         protected void onPreExecute() {
2012             bindView(null, mTarget);
2013         }
2014 
2015         @Override
doInBackground(Void... params)2016         protected UidDetail doInBackground(Void... params) {
2017             return mProvider.getUidDetail(mItem.uids[0], true);
2018         }
2019 
2020         @Override
onPostExecute(UidDetail result)2021         protected void onPostExecute(UidDetail result) {
2022             bindView(result, mTarget);
2023         }
2024     }
2025 
2026     /**
2027      * Test if device has a mobile data radio.
2028      */
hasMobileRadio(Context context)2029     private static boolean hasMobileRadio(Context context) {
2030         if (TEST_RADIOS) {
2031             return SystemProperties.get(TEST_RADIOS_PROP).contains("mobile");
2032         }
2033 
2034         final ConnectivityManager conn = (ConnectivityManager) context.getSystemService(
2035                 Context.CONNECTIVITY_SERVICE);
2036 
2037         // mobile devices should have MOBILE network tracker regardless of
2038         // connection status.
2039         return conn.getNetworkInfo(TYPE_MOBILE) != null;
2040     }
2041 
2042     /**
2043      * Test if device has a mobile 4G data radio.
2044      */
hasMobile4gRadio(Context context)2045     private static boolean hasMobile4gRadio(Context context) {
2046         if (!NetworkPolicyEditor.ENABLE_SPLIT_POLICIES) {
2047             return false;
2048         }
2049         if (TEST_RADIOS) {
2050             return SystemProperties.get(TEST_RADIOS_PROP).contains("4g");
2051         }
2052 
2053         final ConnectivityManager conn = (ConnectivityManager) context.getSystemService(
2054                 Context.CONNECTIVITY_SERVICE);
2055         final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
2056                 Context.TELEPHONY_SERVICE);
2057 
2058         // WiMAX devices should have WiMAX network tracker regardless of
2059         // connection status.
2060         final boolean hasWimax = conn.getNetworkInfo(TYPE_WIMAX) != null;
2061         final boolean hasLte = telephony.getLteOnCdmaMode() == Phone.LTE_ON_CDMA_TRUE;
2062         return hasWimax || hasLte;
2063     }
2064 
2065     /**
2066      * Test if device has a Wi-Fi data radio.
2067      */
hasWifiRadio(Context context)2068     private static boolean hasWifiRadio(Context context) {
2069         if (TEST_RADIOS) {
2070             return SystemProperties.get(TEST_RADIOS_PROP).contains("wifi");
2071         }
2072 
2073         return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI);
2074     }
2075 
2076     /**
2077      * Test if device has an ethernet network connection.
2078      */
hasEthernet(Context context)2079     private static boolean hasEthernet(Context context) {
2080         if (TEST_RADIOS) {
2081             return SystemProperties.get(TEST_RADIOS_PROP).contains("ethernet");
2082         }
2083 
2084         final ConnectivityManager conn = (ConnectivityManager) context.getSystemService(
2085                 Context.CONNECTIVITY_SERVICE);
2086         return conn.getNetworkInfo(TYPE_ETHERNET) != null;
2087     }
2088 
2089     /**
2090      * Inflate a {@link Preference} style layout, adding the given {@link View}
2091      * widget into {@link android.R.id#widget_frame}.
2092      */
inflatePreference(LayoutInflater inflater, ViewGroup root, View widget)2093     private static View inflatePreference(LayoutInflater inflater, ViewGroup root, View widget) {
2094         final View view = inflater.inflate(R.layout.preference, root, false);
2095         final LinearLayout widgetFrame = (LinearLayout) view.findViewById(
2096                 android.R.id.widget_frame);
2097         widgetFrame.addView(widget, new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
2098         return view;
2099     }
2100 
inflateAppTitle( LayoutInflater inflater, ViewGroup root, CharSequence label)2101     private static View inflateAppTitle(
2102             LayoutInflater inflater, ViewGroup root, CharSequence label) {
2103         final TextView view = (TextView) inflater.inflate(
2104                 R.layout.data_usage_app_title, root, false);
2105         view.setText(label);
2106         return view;
2107     }
2108 
2109     /**
2110      * Test if any networks are currently limited.
2111      */
hasLimitedNetworks()2112     private boolean hasLimitedNetworks() {
2113         return !buildLimitedNetworksList().isEmpty();
2114     }
2115 
2116     /**
2117      * Build string describing currently limited networks, which defines when
2118      * background data is restricted.
2119      */
buildLimitedNetworksString()2120     private CharSequence buildLimitedNetworksString() {
2121         final List<CharSequence> limited = buildLimitedNetworksList();
2122 
2123         // handle case where no networks limited
2124         if (limited.isEmpty()) {
2125             limited.add(getText(R.string.data_usage_list_none));
2126         }
2127 
2128         return TextUtils.join(limited);
2129     }
2130 
2131     /**
2132      * Build list of currently limited networks, which defines when background
2133      * data is restricted.
2134      */
buildLimitedNetworksList()2135     private List<CharSequence> buildLimitedNetworksList() {
2136         final Context context = getActivity();
2137         final String subscriberId = getActiveSubscriberId(context);
2138 
2139         // build combined list of all limited networks
2140         final ArrayList<CharSequence> limited = Lists.newArrayList();
2141         if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobileAll(subscriberId))) {
2142             limited.add(getText(R.string.data_usage_list_mobile));
2143         }
2144         if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobile3gLower(subscriberId))) {
2145             limited.add(getText(R.string.data_usage_tab_3g));
2146         }
2147         if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobile4g(subscriberId))) {
2148             limited.add(getText(R.string.data_usage_tab_4g));
2149         }
2150         if (mPolicyEditor.hasLimitedPolicy(buildTemplateWifi())) {
2151             limited.add(getText(R.string.data_usage_tab_wifi));
2152         }
2153         if (mPolicyEditor.hasLimitedPolicy(buildTemplateEthernet())) {
2154             limited.add(getText(R.string.data_usage_tab_ethernet));
2155         }
2156 
2157         return limited;
2158     }
2159 
2160     /**
2161      * Inset both selector and divider {@link Drawable} on the given
2162      * {@link ListView} by the requested dimensions.
2163      */
insetListViewDrawables(ListView view, int insetSide)2164     private static void insetListViewDrawables(ListView view, int insetSide) {
2165         final Drawable selector = view.getSelector();
2166         final Drawable divider = view.getDivider();
2167 
2168         // fully unregister these drawables so callbacks can be maintained after
2169         // wrapping below.
2170         final Drawable stub = new ColorDrawable(Color.TRANSPARENT);
2171         view.setSelector(stub);
2172         view.setDivider(stub);
2173 
2174         view.setSelector(new InsetBoundsDrawable(selector, insetSide));
2175         view.setDivider(new InsetBoundsDrawable(divider, insetSide));
2176     }
2177 
2178     /**
2179      * Set {@link android.R.id#title} for a preference view inflated with
2180      * {@link #inflatePreference(LayoutInflater, ViewGroup, View)}.
2181      */
setPreferenceTitle(View parent, int resId)2182     private static void setPreferenceTitle(View parent, int resId) {
2183         final TextView title = (TextView) parent.findViewById(android.R.id.title);
2184         title.setText(resId);
2185     }
2186 
2187     /**
2188      * Set {@link android.R.id#summary} for a preference view inflated with
2189      * {@link #inflatePreference(LayoutInflater, ViewGroup, View)}.
2190      */
setPreferenceSummary(View parent, CharSequence string)2191     private static void setPreferenceSummary(View parent, CharSequence string) {
2192         final TextView summary = (TextView) parent.findViewById(android.R.id.summary);
2193         summary.setVisibility(View.VISIBLE);
2194         summary.setText(string);
2195     }
2196 
contains(int[] haystack, int needle)2197     private static boolean contains(int[] haystack, int needle) {
2198         for (int value : haystack) {
2199             if (value == needle) {
2200                 return true;
2201             }
2202         }
2203         return false;
2204     }
2205 }
2206