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