Home | History | Annotate | Download | only in settings
      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
    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
    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
    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
    442     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    443         inflater.inflate(R.menu.data_usage, menu);
    444     }
    445 
    446     @Override
    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
    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
    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      */
    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 
    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      */
    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      */
    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      */
    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 
    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      */
    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 
    834     private void setPolicyWarningBytes(long warningBytes) {
    835         if (LOGD) Log.d(TAG, "setPolicyWarningBytes()");
    836         mPolicyEditor.setPolicyWarningBytes(mTemplate, warningBytes);
    837         updatePolicy(false);
    838     }
    839 
    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 
    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 
    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 
    868     private boolean isNetworkPolicyModifiable(NetworkPolicy policy) {
    869         return policy != null && isBandwidthControlEnabled() && mDataEnabled.isChecked()
    870                 && ActivityManager.getCurrentUser() == UserHandle.USER_OWNER;
    871     }
    872 
    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 
    882     private boolean getDataRoaming() {
    883         final ContentResolver resolver = getActivity().getContentResolver();
    884         return Settings.Global.getInt(resolver, Settings.Global.DATA_ROAMING, 0) != 0;
    885     }
    886 
    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 
    895     public void setRestrictBackground(boolean restrictBackground) {
    896         mPolicyManager.setRestrictBackground(restrictBackground);
    897         mMenuRestrictBackground.setChecked(restrictBackground);
    898     }
    899 
    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 
    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      */
    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      */
    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      */
   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
   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
   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 
   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 
   1319         CycleItem(CharSequence label) {
   1320             this.label = label;
   1321         }
   1322 
   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
   1330         public String toString() {
   1331             return label.toString();
   1332         }
   1333 
   1334         @Override
   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
   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 
   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 {
   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 
   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 
   1385         public void setChangePossible(boolean possible) {
   1386             mChangePossible = possible;
   1387             updateChange();
   1388         }
   1389 
   1390         public void setChangeVisible(boolean visible) {
   1391             mChangeVisible = visible;
   1392             updateChange();
   1393         }
   1394 
   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          */
   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 
   1428         public AppItem(int key) {
   1429             this.key = key;
   1430         }
   1431 
   1432         public AppItem(Parcel parcel) {
   1433             key = parcel.readInt();
   1434             uids = parcel.readSparseBooleanArray();
   1435             total = parcel.readLong();
   1436         }
   1437 
   1438         public void addUid(int uid) {
   1439             uids.put(uid, true);
   1440         }
   1441 
   1442         @Override
   1443         public void writeToParcel(Parcel dest, int flags) {
   1444             dest.writeInt(key);
   1445             dest.writeSparseBooleanArray(uids);
   1446             dest.writeLong(total);
   1447         }
   1448 
   1449         @Override
   1450         public int describeContents() {
   1451             return 0;
   1452         }
   1453 
   1454         @Override
   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 
   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          */
   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
   1546         public int getCount() {
   1547             return mItems.size();
   1548         }
   1549 
   1550         @Override
   1551         public Object getItem(int position) {
   1552             return mItems.get(position);
   1553         }
   1554 
   1555         @Override
   1556         public long getItemId(int position) {
   1557             return mItems.get(position).key;
   1558         }
   1559 
   1560         @Override
   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 
   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
   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
   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 
   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
   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 
   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
   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 
   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
   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 
   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
   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 {
   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
   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 {
   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
   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 {
   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
   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 {
   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
   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 {
   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
   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 
   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
   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
   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      */
   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 
   2146         private UidDetailTask(UidDetailProvider provider, AppItem item, View target) {
   2147             mProvider = checkNotNull(provider);
   2148             mItem = checkNotNull(item);
   2149             mTarget = checkNotNull(target);
   2150         }
   2151 
   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 
   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
   2182         protected void onPreExecute() {
   2183             bindView(null, mTarget);
   2184         }
   2185 
   2186         @Override
   2187         protected UidDetail doInBackground(Void... params) {
   2188             return mProvider.getUidDetail(mItem.key, true);
   2189         }
   2190 
   2191         @Override
   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      */
   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      */
   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      */
   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      */
   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      */
   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 
   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      */
   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
   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
   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      */
   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      */
   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      */
   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