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