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