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