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